Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.javascript.jscomp.CodingConvention.Bind; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.regex.Pattern; /** * A peephole optimization that minimizes code by simplifying conditional * expressions, replacing IFs with HOOKs, replacing object constructors * with literals, and simplifying returns. * */ class PeepholeSubstituteAlternateSyntax extends AbstractPeepholeOptimization { private static final CodeGenerator REGEXP_ESCAPER = CodeGenerator.forCostEstimation( null /* blow up if we try to produce code */); private final boolean late; private static final int STRING_SPLIT_OVERHEAD = ".split('.')".length(); static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS = DiagnosticType.warning( "JSC_INVALID_REGULAR_EXPRESSION_FLAGS", "Invalid flags to RegExp constructor: {0}"); /** * @param late When late is false, this mean we are currently running before * most of the other optimizations. In this case we would avoid optimizations * that would make the code harder to analyze (such as using string splitting, * merging statements with commas, etc). When this is true, we would * do anything to minimize for size. */

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> PeepholeSubstituteAlternateSyntax(boolean late) { this.late = late; } /** * Tries apply our various peephole minimizations on the passed in node. */ @Override @SuppressWarnings("fallthrough") public Node optimizeSubtree(Node node) { switch(node.getType()) { case Token.TRUE: case Token.FALSE: return reduceTrueFalse(node); case Token.NEW: node = tryFoldStandardConstructors(node); if (!node.isCall()) { return node; } // Fall through on purpose because tryFoldStandardConstructors() may Node value = callTarget.getNext(); if (value != null && value.getNext() == null && NodeUtil.isImmutableValue(value)) { Node addition = IR.add( IR.string("").srcref(callTarget), value.detachFromParent()); n.getParent().replaceChild(n, addition); reportCodeChange(); return addition; } } return n; } private Node tryFoldImmediateCallToBoundFunction(Node n) { // Rewriting "(fn.bind(a,b))()" to "fn.call(a,b)" makes it inlinable Preconditions.checkState(n.isCall()); Node callTarget = n.getFirstChild(); Bind bind = getCodingConvention().describeFunctionBind(callTarget, false); if (bind != null) { // replace the call target bind.target.detachFromParent(); n.replaceChild(callTarget, bind.target); callTarget = bind.target; // push the parameters addParameterAfter(bind.parameters, callTarget); // add the this value before the parameters if necessary if (bind.thisValue != null && !NodeUtil.isUndefined(bind.thisValue)) { // rewrite from "fn(a, b)" to "fn.call(thisValue, a, b)" Node newCallTarget = IR.getprop( callTarget.cloneTree(), IR.string("call").srcref(callTarget)); n.replaceChild(callTarget, newCallTarget); n.addChildAfter(bind.thisValue.cloneTree(), newCallTarget); n.putBooleanProp(Node.FREE_CALL

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>, false); } else { n.putBooleanProp(Node.FREE_CALL, true); } reportCodeChange(); } return n; } private void addParameterAfter(Node parameterList, Node after) { if (parameterList != null) { // push the last parameter to the head of the list first. addParameterAfter(parameterList.getNext(), after); after.getParent().addChildAfter(parameterList.cloneTree(), after); } } private Node trySplitComma(Node n) { if (late) { return n; } Node parent = n.getParent(); Node left = n.getFirstChild(); Node right = n.getLastChild(); if (parent.isExprResult() && !parent.getParent().isLabel()) { // split comma n.detachChildren(); // Replace the original expression with the left operand. parent.replaceChild(n, left); // Add the right expression afterward. Node newStatement = IR.exprResult(right); newStatement.copyInformationFrom(n); //This modifies outside the subtree, which is not //desirable in a peephole optimization. parent.getParent().addChildAfter(newStatement, parent); reportCodeChange(); return left; } else { return n; } } /** * Use "void 0" in place of "undefined" */ private Node tryReplaceUndefined(Node n) { // TODO(johnlenz): consider doing this as a normalization. if (isASTNormalized() && NodeUtil.isUndefined(n) && !NodeUtil.isLValue(n)) { Node replacement = NodeUtil.newUndefinedNode(n); n.getParent().replaceChild(n, replacement); reportCodeChange(); return replacement; } return n; } /** * Reduce "return undefined" or "return void 0" to simply "return". * * @return The original node, maybe simplified. */ private Node tryReduceReturn(Node n) { Node result = n.getFirstChild(); if (result != null) { switch (result.getType()) { case Token.VOID: Node operand = result.getFirstChild(); if (!mayHaveSideEffects(operand))

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> { n.removeFirstChild(); reportCodeChange(); } break; case Token.NAME: String name = result.getString(); if (name.equals("undefined")) { n.removeFirstChild(); reportCodeChange(); } break; } } return n; } private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS = // String, Number, and Boolean functions return non-object types, whereas // new String, new Number, and new Boolean return object types, so don't // include them here. ImmutableSet.of( "Object", "Array", "RegExp", "Error" ); /** * Fold "new Object()" to "Object()". */ private Node tryFoldStandardConstructors(Node n) { Preconditions.checkState(n.isNew()); // If name normalization has been run then we know that // new Object() does in fact refer to what we think it is // and not some custom-defined Object(). if (isASTNormalized()) { if (n.getFirstChild().isName()) { String className = n.getFirstChild().getString(); if (STANDARD_OBJECT_CONSTRUCTORS.contains(className)) { n.setType(Token.CALL); n.putBooleanProp(Node.FREE_CALL, true); reportCodeChange(); } } } return n; } /** * Replaces a new Array or Object node with an object literal, unless the * call to Array or Object is to a local function with the same name. */ private Node tryFoldLiteralConstructor(Node n) { Preconditions.checkArgument(n.isCall() || n.isNew()); Node constructorNameNode = n.getFirstChild(); Node newLiteralNode = null; // We require the AST to be normalized to ensure that, say, // Object() really refers to the built-in Object constructor // and not a user-defined constructor with the same name. if (isASTNormalized() && Token.NAME == constructorNameNode.getType()) { String className = constructorNameNode.getString(); if ("RegExp".equals(className)) { // "RegExp("boo", "g")" --> /boo/g return tryFoldRegularExpressionConstructor(n

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>); } else { boolean constructorHasArgs = constructorNameNode.getNext() != null; if ("Object".equals(className) && !constructorHasArgs) { // "Object()" --> "{}" newLiteralNode = IR.objectlit(); } else if ("Array".equals(className)) { // "Array(arg0, arg1, ...)" --> "[arg0, arg1, ...]" Node arg0 = constructorNameNode.getNext(); FoldArrayAction action = isSafeToFoldArrayConstructor(arg0); if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS || action == FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS) { newLiteralNode = IR.arraylit(); n.removeChildren(); if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS) { newLiteralNode.addChildrenToFront(arg0); } } } if (newLiteralNode != null) { n.getParent().replaceChild(n, newLiteralNode); reportCodeChange(); return newLiteralNode; } } } return n; } private static enum FoldArrayAction { NOT_SAFE_TO_FOLD, SAFE_TO_FOLD_WITH_ARGS, SAFE_TO_FOLD_WITHOUT_ARGS} /** * Checks if it is safe to fold Array() constructor into []. It can be * obviously done, if the initial constructor has either no arguments or * at least two. The remaining case may be unsafe since Array(number) * actually reserves memory for an empty array which contains number elements. */ private static FoldArrayAction isSafeToFoldArrayConstructor(Node arg) { FoldArrayAction action = FoldArrayAction.NOT_SAFE_TO_FOLD; if (arg == null) { action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS; } else if (arg.getNext() != null) { action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; } else { switch (arg.getType()) { case Token.STRING: // "Array('a')" --> "['a']" action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; break; case Token.NUMBER:

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> // "Array(0)" --> "[]" if (arg.getDouble() == 0) { action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS; } break; case Token.ARRAYLIT: // "Array([args])" --> "[[args]]" action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; break; default: } } return action; } private Node tryFoldRegularExpressionConstructor(Node n) { Node parent = n.getParent(); Node constructor = n.getFirstChild(); Node pattern = constructor.getNext(); // e.g. ^foobar$ Node flags = null != pattern ? pattern.getNext() : null; // e.g. gi if (null == pattern || (null != flags && null != flags.getNext())) { // too few or too many arguments return n; } if (// is pattern folded pattern.isString() // make sure empty pattern doesn't fold to // && !"".equals(pattern.getString()) // NOTE(nicksantos): Make sure that the regexp isn't longer than // 100 chars, or it blows up the regexp parser in Opera 9.2. && pattern.getString().length() < 100 && (null == flags || flags.isString()) // don't escape patterns with Unicode escapes since Safari behaves badly // (read can't parse or crashes) on regex literals with Unicode escapes && (isEcmaScript5OrGreater() || !containsUnicodeEscape(pattern.getString()))) { // Make sure that / is escaped, so that it will fit safely in /brackets/ // and make sure that no LineTerminatorCharacters appear literally inside // the pattern. // pattern is a string value with \\ and similar already escaped pattern = makeForwardSlashBracketSafe(pattern); Node regexLiteral; if (null == flags || "".equals(flags.getString())) { // fold to /foobar/ regexLiteral = IR.regexp(pattern); } else { // fold to /foobar/gi if (!areValidRegexpFlags(flags.getString())) { report(INVALID_REGULAR_EXPRESSION_FLAGS, flags); return n; } if (!areSafeFlagsToFold

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>(flags.getString())) { return n; } n.removeChild(flags); regexLiteral = IR.regexp(pattern, flags); } parent.replaceChild(n, regexLiteral); reportCodeChange(); return regexLiteral; } return n; } private Node reduceTrueFalse(Node n) { if (late) { Node not = IR.not(IR.number(n.isTrue() ? 0 : 1)); not.copyInformationFromForTree(n); n.getParent().replaceChild(n, not); reportCodeChange(); return not; } return n; } private Node tryMinimizeArrayLiteral(Node n) { boolean allStrings = true; for (Node cur = n.getFirstChild(); cur != null; cur = cur.getNext()) { if (!cur.isString()) { allStrings = false; } } if (allStrings) { return tryMinimizeStringArrayLiteral(n); } else { return n; } } private Node tryMinimizeStringArrayLiteral(Node n) { if (!late) { return n; } int numElements = n.getChildCount(); // We save two bytes per element. int saving = numElements * 2 - STRING_SPLIT_OVERHEAD; if (saving <= 0) { return n; } String[] strings = new String[n.getChildCount()]; int idx = 0; for (Node cur = n.getFirstChild(); cur != null; cur = cur.getNext()) { strings[idx++] = cur.getString(); } // These delimiters are chars that appears a lot in the program therefore // probably have a small Huffman encoding. String delimiter = pickDelimiter(strings); if (delimiter != null) { String template = Joiner.on(delimiter).join(strings); Node call = IR.call( IR.getprop( IR.string(template), IR.string("split")), IR.string("" + delimiter)); call.copyInformationFromForTree(n); n.getParent().replaceChild(n, call); reportCodeChange(); return call; } return n; } /** * Find a delimiter that does not occur in

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>BLOCK); cc.endFunction(context == Context.STATEMENT); if (funcNeedsParens) { add(")"); } break; case Token.GETTER_DEF: case Token.SETTER_DEF: Preconditions.checkState(n.getParent().isObjectLit()); Preconditions.checkState(childCount == 1); Preconditions.checkState(first.isFunction()); // Get methods are unnamed Preconditions.checkState(first.getFirstChild().getString().isEmpty()); if (type == Token.GETTER_DEF) { // Get methods have no parameters. Preconditions.checkState(!first.getChildAtIndex(1).hasChildren()); add("get "); } else { // Set methods have one parameter. Preconditions.checkState(first.getChildAtIndex(1).hasOneChild()); add("set "); } // The name is on the GET or SET node. String name = n.getString(); Node fn = first; Node parameters = fn.getChildAtIndex(1); Node body = fn.getLastChild(); // Add the property name. if (!n.isQuotedString() && TokenStream.isJSIdentifier(name) && // do not encode literally any non-literal characters that were // Unicode escaped. NodeUtil.isLatin(name)) { add(name); } else { // Determine if the string is a simple number. double d = getSimpleNumber(name); if (!Double.isNaN(d)) { cc.addNumber(d); } else { addJsString(n); } } add(parameters); add(body, Context.PRESERVE_BLOCK); break; case Token.SCRIPT: case Token.BLOCK: { if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } boolean preserveBlock = context == Context.PRESERVE_BLOCK; if (preserveBlock) { cc.beginBlock(); } boolean preferLineBreaks = type == Token.SCRIPT || (type == Token.BLOCK && !preserveBlock && n.getParent() != null && n.getParent().isScript()); for (Node c = first; c != null; c = c.getNext()) { add(c, Context.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> throw new Error("Unknown type " + type + "\n" + n.toStringTree()); } cc.endSourceMapping(n); } /** * We could use addList recursively here, but sometimes we produce * very deeply nested operators and run out of stack space, so we * just unroll the recursion when possible. * * We assume nodes are left-recursive. */ private void unrollBinaryOperator( Node n, int op, String opStr, Context context, Context rhsContext, int leftPrecedence, int rightPrecedence) { Node firstNonOperator = n.getFirstChild(); while (firstNonOperator.getType() == op) { firstNonOperator = firstNonOperator.getFirstChild(); } addExpr(firstNonOperator, leftPrecedence, context); Node current = firstNonOperator; do { current = current.getParent(); cc.addOp(opStr, true); addExpr(current.getFirstChild().getNext(), rightPrecedence, rhsContext); } while (current != n); } static boolean isSimpleNumber(String s) { int len = s.length(); if (len == 0) { return false; } for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c < '0' || c > '9') { return false; } } return len == 1 || s.charAt(0) != '0'; } static double getSimpleNumber(String s) { if (isSimpleNumber(s)) { try { long l = Long.parseLong(s); if (l < NodeUtil.MAX_POSITIVE_INTEGER_NUMBER) { return l; } } catch (NumberFormatException e) { // The number was too long to parse. Fall through to NaN. } } return Double.NaN; } /** * @return Whether the name is an indirect eval. */ private boolean isIndirectEval(Node n) { return n.isName() && "eval".equals(n.getString()) && !n.getBooleanProp(Node.DIRECT_EVAL); } /** * Adds a block or expression, substituting a VOID with an empty

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.*; /** * Verifies that constants are only assigned a value once. * e.g. var XX = 5; * XX = 3; // error! * XX++; // error! * */ class ConstCheck extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR = DiagnosticType.error( "JSC_CONSTANT_REASSIGNED_VALUE_ERROR", "constant {0} assigned a value more than once"); private final AbstractCompiler compiler; private final Set<Scope.Var> initializedConstants; /** * Creates an instance. */ public ConstCheck(AbstractCompiler compiler) { this.compiler = compiler; this.initializedConstants = new HashSet<Scope.Var>(); } @Override public void process(Node externs, Node root) { Preconditions.checkState(compiler.getLifeCycleStage().isNormalized()); NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: if (parent != null && parent.isVar() && n.hasChildren()) { String name = n.getString(); Scope.Var var =

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Override public RenameStrategy getRenameStrategy() { return RenameStrategy.MAPPED; } } private NameSupplier createNameSupplier( RenameStrategy renameStrategy, BiMap<String, String> previousMappings) { previousMappings = previousMappings != null ? previousMappings : ImmutableBiMap.<String, String>of(); if (renameStrategy == RenameStrategy.STABLE) { return new StableNameSupplier(); } else if (generatePseudoNames) { return new PseudoNameSuppier(renameStrategy); } else { return new ObfuscatedNameSuppier(renameStrategy, previousMappings); } } private NameSupplier createNameSupplier( RenameStrategy renameStrategy, RenamingMap mappings) { Preconditions.checkState(renameStrategy == RenameStrategy.MAPPED); return new MappedNameSupplier(mappings); } private class GatherGenerators extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { JSDocInfo doc = n.getJSDocInfo(); if (doc == null) { return; } int numGeneratorAnnotations = (doc.isConsistentIdGenerator() ? 1 : 0) + (doc.isIdGenerator() ? 1 : 0) + (doc.isStableIdGenerator() ? 1 : 0) + (doc.isMappedIdGenerator() ? 1 : 0); if (numGeneratorAnnotations == 0) { return; } else if (numGeneratorAnnotations > 1) { compiler.report(t.makeError(n, CONFLICTING_GENERATOR_TYPE)); } String name = null; if (n.isAssign()) { name = n.getFirstChild().getQualifiedName(); } else if (n.isVar()) { name = n.getFirstChild().getString(); } else if (n.isFunction()){ name = n.getFirstChild().getString(); if (name.isEmpty()) { return; } } if (doc.isConsistentIdGenerator()) { consistNameMap.put(name, Maps.<String, String>newLinkedHashMap()); nameGenerators.put( name, createNameSupplier( RenameStrategy.CONSISTENT, previousMap.get(

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>name))); } else if (doc.isStableIdGenerator()) { nameGenerators.put( name, createNameSupplier( RenameStrategy.STABLE, previousMap.get(name))); } else if (doc.isIdGenerator()) { nameGenerators.put( name, createNameSupplier( RenameStrategy.INCONSISTENT, previousMap.get(name))); } else if (doc.isMappedIdGenerator()) { NameSupplier supplier = nameGenerators.get(name); if (supplier == null || supplier.getRenameStrategy() != RenameStrategy.MAPPED) { compiler.report(t.makeError(n, MISSING_NAME_MAP_FOR_GENERATOR)); // skip registering the name in the list of Generators if there no // mapping. return; } } else { throw new IllegalStateException("unexpected"); } idGeneratorMaps.put(name, Maps.<String, String>newLinkedHashMap()); } } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, new GatherGenerators()); if (!nameGenerators.isEmpty()) { NodeTraversal.traverse(compiler, root, new ReplaceGenerators()); } } private class ReplaceGenerators extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall()) { return; } String callName = n.getFirstChild().getQualifiedName(); NameSupplier nameGenerator = nameGenerators.get(callName); if (nameGenerator == null) { return; } if (!t.inGlobalScope() && nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) { // Warn about calls not in the global scope. compiler.report(t.makeError(n, NON_GLOBAL_ID_GENERATOR_CALL)); return; } if (nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) { for (Node ancestor : n.getAncestors()) { if (NodeUtil.isControlStructure(ancestor)) { // Warn about conditional calls. compiler.report(t.makeError(n, CONDITIONAL_ID_GENERATOR_CALL)); return; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> } Node arg = n.getFirstChild().getNext(); if (arg.isString()) { String rename = getObfuscatedName( arg, callName, nameGenerator, arg.getString()); parent.replaceChild(n, IR.string(rename)); compiler.reportCodeChange(); } else if (arg.isObjectLit()) { for (Node key : arg.children()) { String rename = getObfuscatedName( key, callName, nameGenerator, key.getString()); key.setString(rename); // Prevent standard renaming by marking the key as quoted. key.putBooleanProp(Node.QUOTED_PROP, true); } arg.detachFromParent(); parent.replaceChild(n, arg); compiler.reportCodeChange(); } // TODO(user): Error on id not a string or object literal. } private String getObfuscatedName( Node id, String callName, NameSupplier nameGenerator, String name) { String rename = null; Map<String, String> idGeneratorMap = idGeneratorMaps.get(callName); String instanceId = getIdForGeneratorNode( nameGenerator.getRenameStrategy() != RenameStrategy.INCONSISTENT, id); if (nameGenerator.getRenameStrategy() == RenameStrategy.CONSISTENT) { Map<String, String> entry = consistNameMap.get(callName); rename = entry.get(instanceId); if (rename == null) { rename = nameGenerator.getName(instanceId, name); entry.put(instanceId, rename); } } else { rename = nameGenerator.getName(instanceId, name); } idGeneratorMap.put(rename, instanceId); return rename; } } /** * @return The serialize map of generators and their ids and their * replacements. */ public String getSerializedIdMappings() { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, Map<String, String>> replacements : idGeneratorMaps.entrySet()) { if (!replacements.getValue().isEmpty()) { sb.append("["); sb.append(replacements.getKey()); sb.append("]\n\n"); for (Map.Entry<String, String> replacement : replacements.getValue().entrySet()) { sb.append

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> void process(Node externs, Node root) { Callback callback = new CheckRequiresForConstructorsCallback(); new NodeTraversal(compiler, callback).traverseRoots(externs, root); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { Callback callback = new CheckRequiresForConstructorsCallback(); new NodeTraversal(compiler, callback).traverseWithScope(scriptRoot, SyntacticScopeCreator.generateUntypedTopScope(compiler)); } // Return true if the name is a class name (starts with an uppercase // character, but is not in all caps). private static boolean isClassName(String name) { return (name != null && name.length() > 1 && Character.isUpperCase(name.charAt(0)) && !name.equals(name.toUpperCase())); } // Return the shortest prefix of the className that refers to a class, // or null if no part refers to a class. private static String getOutermostClassName(String className) { for (String part : className.split("\\.")) { if (isClassName(part)) { return className.substring(0, className.indexOf(part) + part.length()); } } return null; } /** * This class "records" each constructor and goog.require visited and creates * a warning for each new node without an appropriate goog.require node. * */ private class CheckRequiresForConstructorsCallback implements Callback { private final List<String> constructors = Lists.newArrayList(); private final List<String> requires = Lists.newArrayList(); private final List<Node> newNodes = Lists.newArrayList(); @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return parent == null || !parent.isScript() || !t.getInput().isExtern(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.ASSIGN: case Token.VAR: maybeAddConstructor(t, n); break; case Token.FUNCTION: // Exclude function expressions. if (NodeUtil.isStatement(n)) { maybeAddConstructor(t, n); } break; case Token.CALL: visitCall

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Node(n, parent); break; case Token.SCRIPT: visitScriptNode(t); break; case Token.NEW: visitNewNode(t, n); } } private void visitScriptNode(NodeTraversal t) { Set<String> classNames = Sets.newHashSet(); for (Node node : newNodes) { String className = node.getFirstChild().getQualifiedName(); String outermostClassName = getOutermostClassName(className); boolean notProvidedByConstructors = (constructors == null || !constructors.contains(className)); boolean notProvidedByRequires = (requires == null || (!requires.contains(className) && !requires.contains(outermostClassName))); if (notProvidedByConstructors && notProvidedByRequires && !classNames.contains(className)) { compiler.report( t.makeError(node, level, MISSING_REQUIRE_WARNING, className)); classNames.add(className); } } // for the next script, if there is one, we don't want the new, ctor, and // require nodes to spill over. this.newNodes.clear(); this.requires.clear(); this.constructors.clear(); } private void visitCallNode(Node n, Node parent) { String required = codingConvention.extractClassNameIfRequire(n, parent); if (required != null) { requires.add(required); } } private void visitNewNode(NodeTraversal t, Node n) { Node qNameNode = n.getFirstChild(); // If the ctor is something other than a qualified name, ignore it. if (!qNameNode.isQualifiedName()) { return; } // Grab the root ctor namespace. Node nameNode = qNameNode; for (; nameNode.hasChildren(); nameNode = nameNode.getFirstChild()) {} // We only consider programmer-defined constructors that are // global variables, or are defined on global variables. if (!nameNode.isName()) { return; } String name = nameNode.getString(); Scope.Var var = t.getScope().getVar(name); if (var == null || var.isLocal() || var.isExtern()) { return; } newNodes.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Convention.isConstant(variableName); } @Override public boolean isConstantKey(String keyName) { return nextConvention.isConstantKey(keyName); } @Override public boolean isValidEnumKey(String key) { return nextConvention.isValidEnumKey(key); } @Override public boolean isOptionalParameter(Node parameter) { return nextConvention.isOptionalParameter(parameter); } @Override public boolean isVarArgsParameter(Node parameter) { return nextConvention.isVarArgsParameter(parameter); } @Override public boolean isExported(String name, boolean local) { return nextConvention.isExported(name, local); } @Override public final boolean isExported(String name) { return isExported(name, false) || isExported(name, true); } @Override public boolean isPrivate(String name) { return nextConvention.isPrivate(name); } @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { return nextConvention.getClassesDefinedByCall(callNode); } @Override public boolean isSuperClassReference(String propertyName) { return nextConvention.isSuperClassReference(propertyName); } @Override public String extractClassNameIfProvide(Node node, Node parent) { return nextConvention.extractClassNameIfProvide(node, parent); } @Override public String extractClassNameIfRequire(Node node, Node parent) { return nextConvention.extractClassNameIfRequire(node, parent); } @Override public String getExportPropertyFunction() { return nextConvention.getExportPropertyFunction(); } @Override public String getExportSymbolFunction() { return nextConvention.getExportSymbolFunction(); } @Override public List<String> identifyTypeDeclarationCall(Node n) { return nextConvention.identifyTypeDeclarationCall(n); } @Override public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type) { nextConvention.applySubclassRelationship( parentCtor, childCtor, type); } @Override public String getAbstractMethodName() { return nextConvention.getAbstractMethodName(); } @Override public String getSingletonGetterClassName(Node callNode) { return nextConvention.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Convention.getObjectLiteralCast(callNode); } @Override public Collection<String> getIndirectlyDeclaredProperties() { return nextConvention.getIndirectlyDeclaredProperties(); } } /** * The default coding convention. * Should be at the bottom of all proxy chains. */ private static class DefaultCodingConvention implements CodingConvention { private static final long serialVersionUID = 1L; @Override public boolean isConstant(String variableName) { return false; } @Override public boolean isConstantKey(String variableName) { return false; } @Override public boolean isValidEnumKey(String key) { return key != null && key.length() > 0; } @Override public boolean isOptionalParameter(Node parameter) { // be as lax as possible, but this must be mutually exclusive from // var_args parameters. return false; } @Override public boolean isVarArgsParameter(Node parameter) { // be as lax as possible return false; } @Override public boolean isExported(String name, boolean local) { return local && name.startsWith("$super"); } @Override public boolean isExported(String name) { return isExported(name, false) || isExported(name, true); } @Override public boolean isPrivate(String name) { return false; } @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { return null; } @Override public boolean isSuperClassReference(String propertyName) { return false; } @Override public String extractClassNameIfProvide(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String extractClassNameIfRequire(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String getExportPropertyFunction() { return null; } @Override public String getExportSymbolFunction() { return null; } @Override public List<String> identifyTypeDeclarationCall(Node n) { return null; } @Override public void applySubclassRelationship(FunctionType parentCtor,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> code.append('\n'); lineIndex++; lineLength = 0; } } @Override void maybeLineBreak() { maybeCutLine(); } /** * This may start a new line if the current line is longer than the line * length threshold. */ @Override void maybeCutLine() { if (lineLength > lineLengthThreshold) { startNewLine(); } } @Override void endLine() { startNewLine(); } @Override void appendBlockStart() { append(" {"); indent++; } @Override void appendBlockEnd() { endLine(); indent--; append("}"); } @Override void listSeparator() { add(", "); maybeLineBreak(); } @Override void endFunction(boolean statementContext) { super.endFunction(statementContext); if (statementContext) { startNewLine(); } } @Override void beginCaseBody() { super.beginCaseBody(); indent++; endLine(); } @Override void endCaseBody() { super.endCaseBody(); indent--; endStatement(); } @Override void appendOp(String op, boolean binOp) { if (binOp) { if (getLastChar() != ' ' && op.charAt(0) != ',') { append(" "); } append(op); append(" "); } else { append(op); } } /** * If the body of a for loop or the then clause of an if statement has * a single statement, should it be wrapped in a block? * {@inheritDoc} */ @Override boolean shouldPreserveExtraBlocks() { // When pretty-printing, always place the statement in its own block // so it is printed on a separate line. This allows breakpoints to be // placed on the statement. return true; } /** * @return The TRY node for the specified CATCH node. */ private Node getTryForCatch(Node n) { return n.getParent().getParent(); } /** * @return Whether the a line break should be added after the specified * BLOCK. */ @Override boolean breakAfterBlockFor(Node n, boolean isStatement

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Context) { Preconditions.checkState(n.isBlock()); Node parent = n.getParent(); if (parent != null) { int type = parent.getType(); switch (type) { case Token.DO: // Don't break before 'while' in DO-WHILE statements. return false; case Token.FUNCTION: // FUNCTIONs are handled separately, don't break here. return false; case Token.TRY: // Don't break before catch return n != parent.getFirstChild(); case Token.CATCH: // Don't break before finally return !NodeUtil.hasFinally(getTryForCatch(parent)); case Token.IF: // Don't break before else return n == parent.getLastChild(); } } return true; } @Override void endFile() { maybeEndStatement(); } } static class CompactCodePrinter extends MappedCodePrinter { // The CompactCodePrinter tries to emit just enough newlines to stop there // being lines longer than the threshold. Since the output is going to be // gzipped, it makes sense to try to make the newlines appear in similar // contexts so that gzip can encode them for 'free'. // // This version tries to break the lines at 'preferred' places, which are // between the top-level forms. This works because top-level forms tend to // be more uniform than arbitrary legal contexts. Better compression would // probably require explicit modeling of the gzip algorithm. private final boolean lineBreak; private final boolean preferLineBreakAtEndOfFile; private int lineStartPosition = 0; private int preferredBreakPosition = 0; private int prevCutPosition = 0; private int prevLineStartPosition = 0; /** * @param lineBreak break the lines a bit more aggressively * @param lineLengthThreshold The length of a line after which we force * a newline when possible. * @param createSrcMap Whether to gather source position * mapping information when printing. * @param sourceMapDetailLevel A filter to control which nodes get mapped into * the source map. */ private CompactCodePrinter(boolean lineBreak, boolean preferLineBreakAtEndOfFile, int lineLengthThreshold, boolean

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>FreeCode; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); // Code with hidden side-effect code is common, for example // accessing "el.offsetWidth" forces a reflow in browsers, to allow this // will still allowing local dead code removal in general, // protect the "side-effect free" code in the source. // if (protectSideEffectFreeCode) { protectSideEffects(); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverse(compiler, scriptRoot, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) { return; } // Do not try to remove a block or an expr result. We already handle // these cases when we visit the child, and the peephole passes will // fix up the tree in more clever ways when these are removed. if (n.isExprResult() || n.isBlock()) { return; } // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. if (n.isQualifiedName() && n.getJSDocInfo() != null) { return; } boolean isResultUsed = NodeUtil.isExpressionResultUsed(n); boolean isSimpleOp = NodeUtil.isSimpleOperator(n); if (!isResultUsed && (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) { String msg = "This code lacks side-effects. Is there a bug?"; if (n.isString()) { msg = "Is there a missing '+' on the previous line?"; } else if (isSimpleOp) { msg = "The result of the '"

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> + Token.name(n.getType()).toLowerCase() + "' operator is not being used."; } t.getCompiler().report( t.makeError(n, level, USELESS_CODE_ERROR, msg)); // TODO(johnlenz): determine if it is necessary to // try to protect side-effect free statements as well. if (!NodeUtil.isStatement(n)) { problemNodes.add(n); } } } /** * Protect side-effect free nodes by making them parameters * to a extern function call. This call will be removed * after all the optimizations passes have run. */ private void protectSideEffects() { if (!problemNodes.isEmpty()) { addExtern(); for (Node n : problemNodes) { Node name = IR.name(PROTECTOR_FN).srcref(n); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node replacement = IR.call(name).srcref(n); replacement.putBooleanProp(Node.FREE_CALL, true); n.getParent().replaceChild(n, replacement); replacement.addChildToBack(n); } compiler.reportCodeChange(); } } private void addExtern() { Node name = IR.name(PROTECTOR_FN); name.putBooleanProp(Node.IS_CONSTANT_NAME, true); Node var = IR.var(name); // Add "@noalias" so we can strip the method when AliasExternals is enabled. JSDocInfoBuilder builder = new JSDocInfoBuilder(false); builder.recordNoAlias(); var.setJSDocInfo(builder.build(var)); CompilerInput input = compiler.getSynthesizedExternsInput(); input.getAstRoot(compiler).addChildrenToBack(var); compiler.reportCodeChange(); } /** * Remove side-effect sync functions. */ static class StripProtection extends AbstractPostOrderCallback implements CompilerPass { private final AbstractCompiler compiler; StripProtection(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> public void visit(NodeTraversal t, Node n, Node parent) { if (n.isCall()) { Node target = n.getFirstChild(); // TODO(johnlenz): add this to the coding convention // so we can remove goog.reflect.sinkValue as well. if (target.isName() && target.getString().equals(PROTECTOR_FN)) { Node expr = n.getLastChild(); n.detachChildren(); parent.replaceChild(n, expr); } } } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>> second) { if (first.isEmpty()) { return second; } if (second.isEmpty()) { return first; } ImmutableList.Builder<T> builder = ImmutableList.builder(); builder.addAll(first); builder.addAll(second); return builder.build(); } boolean hasAnyTemplateTypesInternal() { for (JSType templateValue : templateValues) { if (templateValue.hasAnyTemplateTypes()) { return true; } } return false; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> paths contain a return statement. * May spuriously report that a return statement is missing. * * @return true if all paths return, converse not necessarily true */ private static boolean fastAllPathsReturnCheck(ControlFlowGraph<Node> cfg) { for (DiGraphEdge<Node, Branch> s : cfg.getImplicitReturn().getInEdges()) { if (!s.getSource().getValue().isReturn()) { return false; } } return true; } @Override public void exitScope(NodeTraversal t) { } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { } /** * Determines if the given scope should explicitly return. All functions * with non-void or non-unknown return types must have explicit returns. * * Exception: Constructors which specifically specify a return type are * used to allow invocation without requiring the "new" keyword. They * have an implicit return type. See unit tests. * * @return If a return type is expected, returns it. Otherwise, returns null. */ private JSType explicitReturnExpected(Node scope) { FunctionType scopeType = JSType.toMaybeFunctionType(scope.getJSType()); if (scopeType == null) { return null; } if (isEmptyFunction(scope)) { return null; } if (scopeType.isConstructor()) { return null; } JSType returnType = scopeType.getReturnType(); if (returnType == null) { return null; } if (!isVoidOrUnknown(returnType)) { return returnType; } return null; } /** * @return {@code true} if function represents a JavaScript function * with an empty body */ private static boolean isEmptyFunction(Node function) { return function.getChildCount() == 3 && !function.getFirstChild().getNext().getNext().hasChildren(); } /** * @return {@code true} if returnType is void, unknown, or a union * containing void or unknown */ private boolean isVoidOrUnknown(JSType returnType) { final JSType voidType = compiler

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> new PrepareAnnotations()); } if (root != null) { NodeTraversal.traverse( compiler, root, new PrepareAnnotations()); } } } /** * Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code. */ private void normalizeNodeTypes(Node n) { normalizeBlocks(n); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { // This pass is run during the CompilerTestCase validation, so this // parent pointer check serves as a more general check. Preconditions.checkState(child.getParent() == n); normalizeNodeTypes(child); } } /** * Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.isControlStructure(n) && !n.isLabel() && !n.isSwitch()) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n, c) && !c.isBlock()) { Node newBlock = IR.block().srcref(n); n.replaceChild(c, newBlock); if (!c.isEmpty()) { newBlock.addChildrenToFront(c); } else { newBlock.setWasEmptyNode(true); } c = newBlock; reportChange(); } } } } /** * Normalize where annotations appear on the AST. Copies * around existing JSDoc annotations as well as internal annotations. */ static class PrepareAnnotations implements NodeTraversal.Callback { PrepareAnnotations() { } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.isObjectLit()) { normalizeObjectLiteralAnnotations(n); } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: annotateCalls(n); break; case Token.FUNCTION: annotateDispatchers(n, parent); break; } } private void normalizeObjectLiteralAnnotations(Node objlit) { Preconditions.checkState

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>(objlit.isObjectLit()); for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) { Node value = key.getFirstChild(); normalizeObjectLiteralKeyAnnotations(objlit, key, value); } } /** * There are two types of calls we are interested in calls without explicit * "this" values (what we are call "free" calls) and direct call to eval. */ private void annotateCalls(Node n) { Preconditions.checkState(n.isCall()); // Keep track of of the "this" context of a call. A call without an // explicit "this" is a free call. Node first = n.getFirstChild(); // ignore cast nodes. while (first.isCast()) { first = first.getFirstChild(); } if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.isName() && "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } } /** * Translate dispatcher info into the property expected node. */ private void annotateDispatchers(Node n, Node parent) { Preconditions.checkState(n.isFunction()); if (parent.getJSDocInfo() != null && parent.getJSDocInfo().isJavaDispatch()) { if (parent.isAssign()) { Preconditions.checkState(parent.getLastChild() == n); n.putBooleanProp(Node.IS_DISPATCHER, true); } } } /** * In the AST that Rhino gives us, it needs to make a distinction * between JsDoc on the object literal node and JsDoc on the object literal * value. For example, * <pre> * var x = { * / JSDOC / * a: 'b', * c: / JSDOC / 'd' * }; * </pre> * * But in few narrow cases (in particular, function literals), it's * a

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> declaredSuper = declaredSuper.toMaybeTemplatizedType().getReferencedType(); } if (declaredSuper != null && !(superObject instanceof UnknownType) && !declaredSuper.isEquivalentTo(superObject)) { if (declaredSuper.isEquivalentTo(getNativeType(OBJECT_TYPE))) { registerMismatch(superObject, declaredSuper, report( t.makeError(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString()))); } else { mismatch(t.getSourceName(), n, "mismatch in declaration of superclass type", superObject, declaredSuper); } // Correct the super type. if (!subCtor.hasCachedValues()) { subCtor.setPrototypeBasedOn(superObject); } } } /** * Expect that the first type can be cast to the second type. The first type * must have some relationship with the second. * * @param t The node traversal. * @param n The node where warnings should point. * @param type The type being cast from. * @param castType The type being cast to. */ void expectCanCast(NodeTraversal t, Node n, JSType castType, JSType type) { if (!type.canCastTo(castType)) { registerMismatch(type, castType, report(t.makeError(n, INVALID_CAST, type.toString(), castType.toString()))); } } /** * Expect that the given variable has not been declared with a type. * * @param sourceName The name of the source file we're in. * @param n The node where warnings should point to. * @param parent The parent of {@code n}. * @param var The variable that we're checking. * @param variableName The name of the variable. * @param newType The type being applied to the variable. Mostly just here * for the benefit of the warning. * @return The variable we end up with. Most of the time, this will just * be {@code var}, but in some rare cases we will need to declare * a new var with new source info. */ Var expectUndeclaredVariable(String sourceName, CompilerInput input, Node n, Node parent, Var

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> var, String variableName, JSType newType) { Var newVar = var; boolean allowDupe = false; if (n.isGetProp() || NodeUtil.isObjectLitKey(n)) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); } JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator later in the // compilation process. if (varType != null && varType != typeRegistry.getNativeType(UNKNOWN_TYPE) && newType != null && newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) { // If there are two typed declarations of the same variable, that // is an error and the second declaration is ignored, except in the // case of native types. A null input type means that the declaration // was made in TypedScopeCreator#createInitialScope and is a // native type. We should redeclare it at the new input site. if (var.input == null) { Scope s = var.getScope(); s.undeclare(var); newVar = s.declare(variableName, n, varType, input, false); n.setJSType(varType); if (parent.isVar()) { if (n.getFirstChild() != null) { n.getFirstChild().setJSType(varType); } } else { Preconditions.checkState(parent.isFunction()); parent.setJSType(varType); } } else { // Always warn about duplicates if the overridden type does not // match the original type. // // If the types match, suppress the warning iff there was a @suppress // tag, or if the original declaration was a stub. if (!(allowDupe || var.getParentNode().isExprResult()) || !newType.isEquivalentTo(varType)) { report(JSError.make(sourceName, n, DUP_VAR_DECLARATION, variableName, newType.toString(), var.getInputName(),

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> // Check for the typeof operator. int operatorToken = condition.getType(); switch (operatorToken) { case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.CASE: Node left; Node right; if (operatorToken == Token.CASE) { left = condition.getParent().getFirstChild(); // the switch condition right = condition.getFirstChild(); } else { left = condition.getFirstChild(); right = condition.getLastChild(); } Node typeOfNode = null; Node stringNode = null; if (left.isTypeOf() && right.isString()) { typeOfNode = left; stringNode = right; } else if (right.isTypeOf() && left.isString()) { typeOfNode = right; stringNode = left; } if (typeOfNode != null && stringNode != null) { Node operandNode = typeOfNode.getFirstChild(); JSType operandType = getTypeIfRefinable(operandNode, blindScope); if (operandType != null) { boolean resultEqualsValue = operatorToken == Token.EQ || operatorToken == Token.SHEQ || operatorToken == Token.CASE; if (!outcome) { resultEqualsValue = !resultEqualsValue; } return caseTypeOf(operandNode, operandType, stringNode.getString(), resultEqualsValue, blindScope); } } } switch (operatorToken) { case Token.AND: if (outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } case Token.OR: if (!outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } case Token.EQ: if (outcome) { return caseEquality(condition, blindScope, EQ); } else { return caseEquality(condition, blindScope, NE);

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> } case Token.NE: if (outcome) { return caseEquality(condition, blindScope, NE); } else { return caseEquality(condition, blindScope, EQ); } case Token.SHEQ: if (outcome) { return caseEquality(condition, blindScope, SHEQ); } else { return caseEquality(condition, blindScope, SHNE); } case Token.SHNE: if (outcome) { return caseEquality(condition, blindScope, SHNE); } else { return caseEquality(condition, blindScope, SHEQ); } case Token.NAME: case Token.GETPROP: return caseNameOrGetProp(condition, blindScope, outcome); case Token.ASSIGN: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild().getNext(), blindScope, outcome), outcome); case Token.NOT: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), blindScope, !outcome); case Token.LE: case Token.LT: case Token.GE: case Token.GT: if (outcome) { return caseEquality(condition, blindScope, ineq); } break; case Token.INSTANCEOF: return caseInstanceOf( condition.getFirstChild(), condition.getLastChild(), blindScope, outcome); case Token.IN: if (outcome && condition.getFirstChild().isString()) { return caseIn(condition.getLastChild(), condition.getFirstChild().getString(), blindScope); } break; case Token.CASE: Node left = condition.getParent().getFirstChild(); // the switch condition Node right = condition.getFirstChild(); if (outcome) { return caseEquality(left, right, blindScope, SHEQ); } else { return caseEquality(left, right, blindScope, SHNE); } } return nextPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } private FlowScope caseEquality(Node condition, FlowScope blindScope, Function<TypePair, TypePair> merging) { return caseEquality(condition.getFirstChild(), condition.getLastChild(), blind

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.javascript.rhino.Node; /** * An abstract class whose implementations run peephole optimizations: * optimizations that look at a small section of code and either remove * that code (if it is not needed) or replaces it with smaller code. * */ abstract class AbstractPeepholeOptimization { private AbstractCompiler compiler; /** * Given a node to optimize and a traversal, optimize the node. Subclasses * should override to provide their own peephole optimization. * * @param subtree The subtree that will be optimized. * @return The new version of the subtree (or null if the subtree or one of * its parents was removed from the AST). If the subtree has not changed, * this method must return {@code subtree}. */ abstract Node optimizeSubtree(Node subtree); /** * Helper method for reporting an error to the compiler when applying a * peephole optimization. * * @param diagnostic The error type * @param n The node for which the error should be reported */ protected void report(DiagnosticType diagnostic, Node n) { JSError error = JSError.make(NodeUtil.getSourceName(n), n, diagnostic, n.toString()); compiler.report(error); } /** * Helper method for telling the compiler that something has changed. * Subclasses must call these if they have changed the AST. */ protected void reportCodeChange() {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Preconditions.checkNotNull(compiler); compiler.reportCodeChange(); } /** * Are the nodes equal for the purpose of inlining? * If type aware optimizations are on, type equality is checked. */ protected boolean areNodesEqualForInlining(Node n1, Node n2) { /* Our implementation delegates to the compiler. We provide this * method because we don't want to expose Compiler to PeepholeOptimizations. */ Preconditions.checkNotNull(compiler); return compiler.areNodesEqualForInlining(n1, n2); } /** * Is the current AST normalized? (e.g. has the Normalize pass been run * and has the Denormalize pass not yet been run?) */ protected boolean isASTNormalized() { Preconditions.checkNotNull(compiler); return compiler.getLifeCycleStage().isNormalized(); } /** * Informs the optimization that a traversal will begin. */ void beginTraversal(AbstractCompiler compiler) { this.compiler = compiler; } /** * Informs the optimization that a traversal has completed. * @param compiler The current compiler. */ void endTraversal(AbstractCompiler compiler) { this.compiler = null; } // NodeUtil's mayEffectMutableState and mayHaveSideEffects need access to the // compiler object, route them through here to give them access. /** * @return Whether the node may create new mutable state, or change existing * state. */ boolean mayEffectMutableState(Node n) { return NodeUtil.mayEffectMutableState(n, compiler); } /** * @return Whether the node may have side effects when executed. */ boolean mayHaveSideEffects(Node n) { return NodeUtil.mayHaveSideEffects(n, compiler); } /** * Returns true if the current node's type implies side effects. * * This is a non-recursive version of the may have side effects * check; used to check wherever the current node's type is one of * the reason's why a subtree has side effects. */ boolean nodeTypeMayHaveSideEffects(Node n) { return NodeUtil.nodeTypeMayHaveSideEffects(n, compiler); } /** * @return Whether the source code version is ECMAScript

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>5 or later. * Workarounds for quirks in browsers that do not support ES5 can be * ignored when this is true. */ boolean isEcmaScript5OrGreater() { return compiler != null && compiler.acceptEcmaScript5(); } /** * @return the current coding convention. */ CodingConvention getCodingConvention() { // Note: this assumes a thread safe coding convention object. return compiler.getCodingConvention(); } /** * Check if the specified node is null or is still in the AST. */ @VisibleForTesting static Node validateResult(Node n) { done: { if (n != null && !n.isScript() && (!n.isBlock() || !n.isSyntheticBlock())) { for (Node parent : n.getAncestors()) { if (parent.isScript()) { break done; } } Preconditions.checkState(false); } } return n; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>_THIS = DiagnosticType.warning( "JSC_USED_GLOBAL_THIS", "dangerous use of the global 'this' object"); private final AbstractCompiler compiler; /** * If {@code assignLhsChild != null}, then the node being traversed is * a descendant of the first child of an ASSIGN node. assignLhsChild's * parent is this ASSIGN node. */ private Node assignLhsChild = null; CheckGlobalThis(AbstractCompiler compiler) { this.compiler = compiler; } /** * Since this pass reports errors only when a global {@code this} keyword * is encountered, there is no reason to traverse non global contexts. */ @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { // Don't traverse functions that are constructors or have the @this // or @override annotation. JSDocInfo jsDoc = getFunctionJsDocInfo(n); if (jsDoc != null && (jsDoc.isConstructor() || jsDoc.isInterface() || jsDoc.hasThisType() || jsDoc.isOverride())) { return false; } // Don't traverse functions unless they would normally // be able to have a @this annotation associated with them. e.g., // var a = function() { }; // or // function a() {} // or // a.x = function() {}; // or // var a = {x: function() {}}; int pType = parent.getType(); if (!(pType == Token.BLOCK || pType == Token.SCRIPT || pType == Token.NAME || pType == Token.ASSIGN || // object literal keys pType == Token.STRING_KEY)) { return false; } // Don't traverse functions that are getting lent to a prototype. Node gramps = parent.getParent(); if (NodeUtil.isObjectLitKey(parent)) { JSDocInfo maybeLends = gramps.getJSDocInfo(); if (maybeLends != null && maybeLends.getLendsName() != null && maybeLends.getLendsName().endsWith(".prototype")) { return false; } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> if (parent != null && parent.isAssign()) { Node lhs = parent.getFirstChild(); if (n == lhs) { // Always traverse the left side of the assignment. To handle // nested assignments properly (e.g., (a = this).property = c;), // assignLhsChild should not be overridden. if (assignLhsChild == null) { assignLhsChild = lhs; } } else { // Only traverse the right side if it's not an assignment to a prototype // property or subproperty. if (NodeUtil.isGet(lhs)) { if (lhs.isGetProp() && lhs.getLastChild().getString().equals("prototype")) { return false; } Node llhs = lhs.getFirstChild(); if (llhs.isGetProp() && llhs.getLastChild().getString().equals("prototype")) { return false; } } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isThis() && shouldReportThis(n)) { compiler.report(t.makeError(n, GLOBAL_THIS)); } if (n == assignLhsChild) { assignLhsChild = null; } } private boolean shouldReportThis(Node n) { Node parent = n.getParent(); if (assignLhsChild != null) { // Always report a THIS on the left side of an assign. return true; } // Also report a THIS with a property access. return parent != null && NodeUtil.isGet(parent); } /** * Gets a function's JSDoc information, if it has any. Checks for a few * patterns (ellipses show where JSDoc would be): * <pre> * ... function() {} * ... x = function() {}; * var ... x = function() {}; * ... var x = function() {}; * </pre> */ private JSDocInfo getFunctionJsDocInfo(Node n) { JSDocInfo jsDoc = n.getJSDocInfo(); Node parent = n.getParent(); if (jsDoc == null) { int parentType = parent.getType(); if

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> (parentType == Token.NAME || parentType == Token.ASSIGN) { jsDoc = parent.getJSDocInfo(); if (jsDoc == null && parentType == Token.NAME) { Node gramps = parent.getParent(); if (gramps.isVar()) { jsDoc = gramps.getJSDocInfo(); } } } } return jsDoc; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> public void process(Node externs, Node root) { if (namespace == null) { namespace = new GlobalNamespace(compiler, root); } overrideDefines(collectDefines(root, namespace)); } private void overrideDefines(Map<String, DefineInfo> allDefines) { boolean changed = false; for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) { String defineName = def.getKey(); DefineInfo info = def.getValue(); Node inputValue = dominantReplacements.get(defineName); Node finalValue = inputValue != null ? inputValue : info.getLastValue(); if (finalValue != info.initialValue) { info.initialValueParent.replaceChild( info.initialValue, finalValue.cloneTree()); compiler.addToDebugLog("Overriding @define variable " + defineName); changed = changed || finalValue.getType() != info.initialValue.getType() || !finalValue.isEquivalentTo(info.initialValue); } } if (changed) { compiler.reportCodeChange(); } Set<String> unusedReplacements = dominantReplacements.keySet(); unusedReplacements.removeAll(allDefines.keySet()); unusedReplacements.removeAll(KNOWN_DEFINES); for (String unknownDefine : unusedReplacements) { compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine)); } } private static String format(MessageFormat format, Object... params) { return format.format(params); } /** * Only defines of literal number, string, or boolean are supported. */ private boolean isValidDefineType(JSTypeExpression expression) { JSType type = expression.evaluate(null, compiler.getTypeRegistry()); return !type.isUnknownType() && type.isSubtype( compiler.getTypeRegistry().getNativeType( JSTypeNative.NUMBER_STRING_BOOLEAN)); } /** * Finds all defines, and creates a {@link DefineInfo} data structure for * each one. * @return A map of {@link DefineInfo} structures, keyed by name. */ private Map<String, DefineInfo> collectDefines(Node root, GlobalNamespace namespace) { // Find all the global names with a @define annotation

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> List<Name> allDefines = Lists.newArrayList(); for (Name name : namespace.getNameIndex().values()) { Ref decl = name.getDeclaration(); if (name.docInfo != null && name.docInfo.isDefine()) { // Process defines should not depend on check types being enabled, // so we look for the JSDoc instead of the inferred type. if (isValidDefineType(name.docInfo.getType())) { allDefines.add(name); } else { JSError error = JSError.make( decl.getSourceName(), decl.node, INVALID_DEFINE_TYPE_ERROR); compiler.report(error); } } else { for (Ref ref : name.getRefs()) { if (ref == decl) { // Declarations were handled above. continue; } Node n = ref.node; Node parent = ref.node.getParent(); JSDocInfo info = n.getJSDocInfo(); if (info == null && parent.isVar() && parent.hasOneChild()) { info = parent.getJSDocInfo(); } if (info != null && info.isDefine()) { allDefines.add(name); break; } } } } CollectDefines pass = new CollectDefines(compiler, allDefines); NodeTraversal.traverse(compiler, root, pass); return pass.getAllDefines(); } /** * Finds all assignments to @defines, and figures out the last value of * the @define. */ private static final class CollectDefines implements Callback { private final AbstractCompiler compiler; private final Map<String, DefineInfo> assignableDefines; private final Map<String, DefineInfo> allDefines; private final Map<Node, RefInfo> allRefInfo; // A hack that allows us to remove ASSIGN/VAR statements when // we're currently visiting one of the children of the assign. private Node lvalueToRemoveLater = null; // A stack tied to the node traversal, to keep track of whether // we're in a conditional block. If 1 is at the top, assignment to // a define is allowed. Otherwise, it's not allowed. private final Deque<Integer> assignAllowed; CollectDefines(AbstractCompiler compiler, List<

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Name> listOfDefines) { this.compiler = compiler; this.allDefines = Maps.newHashMap(); assignableDefines = Maps.newHashMap(); assignAllowed = new ArrayDeque<Integer>(); assignAllowed.push(1); // Create a map of references to defines keyed by node for easy lookup allRefInfo = Maps.newHashMap(); for (Name name : listOfDefines) { Ref decl = name.getDeclaration(); if (decl != null) { allRefInfo.put(decl.node, new RefInfo(decl, name)); } for (Ref ref : name.getRefs()) { if (ref == decl) { // Declarations were handled above. continue; } // If there's a TWIN def, only put one of the twins in. if (ref.getTwin() == null || !ref.getTwin().isSet()) { allRefInfo.put(ref.node, new RefInfo(ref, name)); } } } } /** * Get a map of {@link DefineInfo} structures, keyed by the name of * the define. */ Map<String, DefineInfo> getAllDefines() { return allDefines; } /** * Keeps track of whether the traversal is in a conditional branch. * We traverse all nodes of the parse tree. */ @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { updateAssignAllowedStack(n, true); return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { RefInfo refInfo = allRefInfo.get(n); if (refInfo != null) { Ref ref = refInfo.ref; Name name = refInfo.name; String fullName = name.getFullName(); switch (ref.type) { case SET_FROM_GLOBAL: case SET_FROM_LOCAL: Node valParent = getValueParent(ref); Node val = valParent.getLastChild(); if (valParent.isAssign() && name.isSimpleName() && name.getDeclaration() == ref) { // For defines, it's an error if a simple name is assigned // before it's declared compiler.report( t

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName)); } else if (processDefineAssignment(t, fullName, val, valParent)) { // remove the assignment so that the variable is still declared, // but no longer assigned to a value, e.g., // DEF_FOO = 5; // becomes "5;" // We can't remove the ASSIGN/VAR when we're still visiting its // children, so we'll have to come back later to remove it. refInfo.name.removeRef(ref); lvalueToRemoveLater = valParent; } break; default: if (t.inGlobalScope()) { // Treat this as a reference to a define in the global scope. // After this point, the define must not be reassigned, // or it's an error. DefineInfo info = assignableDefines.get(fullName); if (info != null) { setDefineInfoNotAssignable(info, t); assignableDefines.remove(fullName); } } break; } } if (!t.inGlobalScope() && n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) { // warn about @define annotations in local scopes compiler.report( t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, "")); } if (lvalueToRemoveLater == n) { lvalueToRemoveLater = null; if (n.isAssign()) { Node last = n.getLastChild(); n.removeChild(last); parent.replaceChild(n, last); } else { Preconditions.checkState(n.isName()); n.removeChild(n.getFirstChild()); } compiler.reportCodeChange(); } if (n.isCall()) { if (t.inGlobalScope()) { // If there's a function call in the global scope, // we just say it's unsafe and freeze all the defines. // // NOTE(nicksantos): We could be a lot smarter here. For example, // ReplaceOverriddenVars keeps a call graph of all functions and // which functions/variables that they reference, and tries // to statically determine which functions are "safe" and which // are

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> First declaration of this define. info = new DefineInfo(value, valueParent); allDefines.put(name, info); assignableDefines.put(name, info); } else if (info.recordAssignment(value)) { // The define was already initialized, but this is a safe // re-assignment. return true; } else { // The define was already initialized, and this is an unsafe // re-assignment. compiler.report( t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR, name, info.getReasonWhyNotAssignable())); } } return false; } /** * Gets the parent node of the value for any assignment to a Name. * For example, in the assignment * {@code var x = 3;} * the parent would be the NAME node. */ private static Node getValueParent(Ref ref) { // there are two types of declarations: VARs and ASSIGNs return ref.node.getParent() != null && ref.node.getParent().isVar() ? ref.node : ref.node.getParent(); } /** * Records the fact that because of the current node in the node traversal, * the define can't ever be assigned again. * * @param info Represents the define variable. * @param t The current traversal. */ private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) { info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE, t.getLineNumber(), t.getSourceName())); } /** * A simple data structure for associating a Ref with the name * that it references. */ private static class RefInfo { final Ref ref; final Name name; RefInfo(Ref ref, Name name) { this.ref = ref; this.name = name; } } } /** * A simple class for storing information about a define. * Gathers the initial value, the last assigned value, and whether * the define can be safely assigned a new value. */ private static final class DefineInfo { public final Node initialValueParent; public final Node initialValue; private Node lastValue; private boolean isAssignable; private String reasonNotAssignable;

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>: elements are {@code null} until requested by * {@link #getControlFlowGraph()}. Note that {@link ArrayDeque} does not allow * {@code null} elements, so {@link LinkedList} is used instead. */ Deque<ControlFlowGraph<Node>> cfgs = new LinkedList<ControlFlowGraph<Node>>(); /** The current source file name */ private String sourceName; /** The current input */ private InputId inputId; /** The scope creator */ private ScopeCreator scopeCreator; /** Possible callback for scope entry and exist **/ private ScopedCallback scopeCallback; /** Callback for passes that iterate over a list of functions */ public interface FunctionCallback { void visit(AbstractCompiler compiler, Node fnRoot); } /** * Callback for tree-based traversals */ public interface Callback { /** * <p>Visits a node in pre order (before visiting its children) and decides * whether this node's children should be traversed. If children are * traversed, they will be visited by * {@link #visit(NodeTraversal, Node, Node)} in postorder.</p> * <p>Implementations can have side effects (e.g. modifying the parse * tree).</p> * @return whether the children of this node should be visited */ boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent); /** * <p>Visits a node in postorder (after its children have been visited). * A node is visited only if all its parents should be traversed * ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p> * <p>Implementations can have side effects (e.g. modifying the parse * tree).</p> */ void visit(NodeTraversal t, Node n, Node parent); } /** * Callback that also knows about scope changes */ public interface ScopedCallback extends Callback { /** * Called immediately after entering a new scope. The new scope can * be accessed through t.getScope() */ void enterScope(NodeTraversal t); /** * Called immediately before exiting a scope. The ending scope can * be accessed through t.getScope() */ void exitScope(NodeTraversal t); } /** * Abstract callback to

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> visit all nodes in postorder. */ public abstract static class AbstractPostOrderCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return true; } } /** Abstract callback to visit all nodes in preorder. */ public abstract static class AbstractPreOrderCallback implements Callback { @Override public void visit(NodeTraversal t, Node n, Node parent) {} } /** * Abstract scoped callback to visit all nodes in postorder. */ public abstract static class AbstractScopedCallback implements ScopedCallback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return true; } @Override public void enterScope(NodeTraversal t) {} @Override public void exitScope(NodeTraversal t) {} } /** * Abstract callback to visit all nodes but not traverse into function * bodies. */ public abstract static class AbstractShallowCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || !parent.isFunction() || n == parent.getFirstChild(); } } /** * Abstract callback to visit all structure and statement nodes but doesn't * traverse into functions or expressions. */ public abstract static class AbstractShallowStatementCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return parent == null || NodeUtil.isControlStructure(parent) || NodeUtil.isStatementBlock(parent); } } /** * Abstract callback to visit a pruned set of nodes. */ public abstract static class AbstractNodeTypePruningCallback implements Callback { private final Set<Integer> nodeTypes; private final boolean include; /** * Creates an abstract pruned callback. * @param nodeTypes the nodes to include in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) { this(nodeTypes, true); } /** * Creates an abstract pruned callback. * @param node

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Types the nodes to include/exclude in the traversal * @param include whether to include or exclude the nodes in the traversal */ public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes, boolean include) { this.nodeTypes = nodeTypes; this.include = include; } @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { return include == nodeTypes.contains(n.getType()); } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb; if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.inputId = null; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (inputId != null) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false, false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> traverse(Node root) { try { inputId = NodeUtil.getInputId(root); sourceName = ""; curNode = root; pushScope(root); // null parent ensures that the shallow callbacks will traverse root traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; } try { Node scopeRoot = roots.get(0).getParent(); Preconditions.checkState(scopeRoot != null); inputId = NodeUtil.getInputId(scopeRoot); sourceName = ""; curNode = scopeRoot; pushScope(scopeRoot); for (Node root : roots) { Preconditions.checkState(root.getParent() == scopeRoot); traverseBranch(root, scopeRoot); } popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } private static final String MISSING_SOURCE = "[source unknown]"; private String formatNodePosition(Node n) { String sourceFileName = getBestSourceFileName(n); if (sourceFileName == null) { return MISSING_SOURCE + "\n"; } int lineNumber = n.getLineno(); int columnNumber = n.getCharno(); String src = compiler.getSourceLine(sourceFileName, lineNumber); if (src == null) { src = MISSING_SOURCE; } return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n" + src + "\n"; } /** * Traverses a parse tree recursively with a scope, starting with the given * root. This should only be used in the global scope. Otherwise, use * {@link #traverseAtScope}. */ void traverseWithScope(Node root, Scope s) { Preconditions.checkState(s.isGlobal()); inputId = null; sourceName = ""; curNode = root; pushScope(s); traverseBranch(root, null); popScope(); } /** * Traverses a parse tree recursively with a scope

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>, starting at that scope's * root. */ void traverseAtScope(Scope s) { Node n = s.getRootNode(); if (n.isFunction()) { // We need to do some extra magic to make sure that the scope doesn't // get re-created when we dive into the function. if (inputId == null) { inputId = NodeUtil.getInputId(n); } sourceName = getSourceName(n); curNode = n; pushScope(s); Node args = n.getFirstChild().getNext(); Node body = args.getNext(); traverseBranch(args, n); traverseBranch(body, n); popScope(); } else { traverseWithScope(n, s); } } /** * Traverses an inner node recursively with a refined scope. An inner node may * be any node with a non {@code null} parent (i.e. all nodes except the * root). * * @param node the node to traverse * @param parent the node's parent, it may not be {@code null} * @param refinedScope the refined scope of the scope currently at the top of * the scope stack or in trivial cases that very scope or {@code null} */ protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) { Preconditions.checkNotNull(parent); if (refinedScope != null && getScope() != refinedScope) { curNode = node; pushScope(refinedScope); traverseBranch(node, parent); popScope(); } else { traverseBranch(node, parent); } } public AbstractCompiler getCompiler() { return compiler; } /** * Gets the current line number, or zero if it cannot be determined. The line * number is retrieved lazily as a running time optimization. */ public int getLineNumber() { Node cur = curNode; while (cur != null) { int line = cur.getLineno(); if (line >= 0) { return line; } cur = cur.getParent(); } return 0; } /** * Gets the current input source name. * * @return A string that may be empty,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.traverse(root); } /** * Traverses a list of node trees. */ public static void traverseRoots( AbstractCompiler compiler, List<Node> roots, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } public static void traverseRoots( AbstractCompiler compiler, Callback cb, Node ... roots) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { inputId = n.getInputId(); sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) { return; } if (type == Token.FUNCTION) { traverseFunction(n, parent); } else { for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } } curNode = n; callback.visit(this, n, parent); } /** Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.isFunction()); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression names are only accessible within the function // scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverseBranch(args, n); // Body Preconditions.checkState(body.getNext() == null && body.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Node catchBody = block().copyInformationFrom(tryBody); return new Node(Token.TRY, tryBody, catchBody, finallyBody); } public static Node tryCatch(Node tryBody, Node catchNode) { Preconditions.checkState(tryBody.isBlock()); Preconditions.checkState(catchNode.isCatch()); Node catchBody = blockUnchecked(catchNode).copyInformationFrom(catchNode); return new Node(Token.TRY, tryBody, catchBody); } public static Node tryCatchFinally( Node tryBody, Node catchNode, Node finallyBody) { Preconditions.checkState(finallyBody.isBlock()); Node tryNode = tryCatch(tryBody, catchNode); tryNode.addChildToBack(finallyBody); return tryNode; } public static Node catchNode(Node expr, Node body) { Preconditions.checkState(expr.isName()); Preconditions.checkState(body.isBlock()); return new Node(Token.CATCH, expr, body); } public static Node breakNode() { return new Node(Token.BREAK); } public static Node breakNode(Node name) { // TODO(johnlenz): additional validation here. Preconditions.checkState(name.isLabelName()); return new Node(Token.BREAK, name); } public static Node continueNode() { return new Node(Token.CONTINUE); } public static Node continueNode(Node name) { // TODO(johnlenz): additional validation here. Preconditions.checkState(name.isLabelName()); return new Node(Token.CONTINUE, name); } // public static Node call(Node target, Node ... args) { Node call = new Node(Token.CALL, target); for (Node arg : args) { Preconditions.checkState(mayBeExpression(arg)); call.addChildToBack(arg); } return call; } public static Node newNode(Node target, Node ... args) { Node newcall = new Node(Token.NEW, target); for (Node arg : args) { Preconditions.checkState(mayBeExpression(arg)); newcall.addChildToBack(arg); } return newcall; } public static Node name(String name) { return Node.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>newString(Token.NAME, name); } public static Node getprop(Node target, Node prop) { Preconditions.checkState(mayBeExpression(target)); Preconditions.checkState(prop.isString()); return new Node(Token.GETPROP, target, prop); } public static Node getelem(Node target, Node elem) { Preconditions.checkState(mayBeExpression(target)); Preconditions.checkState(mayBeExpression(elem)); return new Node(Token.GETELEM, target, elem); } public static Node assign(Node target, Node expr) { Preconditions.checkState(isAssignmentTarget(target)); Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.ASSIGN, target, expr); } public static Node hook(Node cond, Node trueval, Node falseval) { Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState(mayBeExpression(trueval)); Preconditions.checkState(mayBeExpression(falseval)); return new Node(Token.HOOK, cond, trueval, falseval); } public static Node comma(Node expr1, Node expr2) { return binaryOp(Token.COMMA, expr1, expr2); } public static Node and(Node expr1, Node expr2) { return binaryOp(Token.AND, expr1, expr2); } public static Node or(Node expr1, Node expr2) { return binaryOp(Token.OR, expr1, expr2); } public static Node not(Node expr1) { return unaryOp(Token.NOT, expr1); } /** * "==" */ public static Node eq(Node expr1, Node expr2) { return binaryOp(Token.EQ, expr1, expr2); } /** * "===" */ public static Node sheq(Node expr1, Node expr2) { return binaryOp(Token.SHEQ, expr1, expr2); } public static Node voidNode(Node expr1) { return unaryOp(Token.VOID, expr1); } public static Node neg(Node expr1) { return unaryOp(Token.NEG, expr1); } public static Node pos(Node expr1)

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> * so make a best guess. */ private static boolean mayBeStatement(Node n) { if (!mayBeStatementNoReturn(n)) { return n.isReturn(); } return true; } /** * It isn't possible to always determine if a detached node is a expression, * so make a best guess. */ private static boolean mayBeExpression(Node n) { switch (n.getType()) { case Token.FUNCTION: // FUNCTION is used both in expression and statement // contexts. return true; case Token.ADD: case Token.AND: case Token.ARRAYLIT: case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.BITAND: case Token.BITOR: case Token.BITNOT: case Token.BITXOR: case Token.CALL: case Token.CAST: case Token.COMMA: case Token.DEC: case Token.DELPROP: case Token.DIV: case Token.EQ: case Token.FALSE: case Token.GE: case Token.GETPROP: case Token.GETELEM: case Token.GT: case Token.HOOK: case Token.IN: case Token.INC: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NAME: case Token.NE: case Token.NEG: case Token.NEW: case Token.NOT: case Token.NUMBER: case Token.NULL: case Token.OBJECTLIT: case Token.OR: case Token.POS: case Token.REGEXP: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.STRING: case Token.SUB: case Token.THIS: case

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.processForTesting(externsRoot, mainRoot); } hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); aggregateWarningCount += errorManagers[i].getWarningCount(); aggregateWarnings.addAll(Lists.newArrayList(compiler.getWarnings())); if (normalizeEnabled) { boolean verifyDeclaredConstants = true; new Normalize.VerifyConstants(compiler, verifyDeclaredConstants) .process(externsRoot, mainRoot); } } } if (error == null) { assertEquals( "Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()), 0, compiler.getErrorCount()); // Verify the symbol table. ErrorManager symbolTableErrorManager = new BlackHoleErrorManager(compiler); Node expectedRoot = null; if (expected != null) { expectedRoot = parseExpectedJs(expected); expectedRoot.detachFromParent(); } JSError[] stErrors = symbolTableErrorManager.getErrors(); if (expectedSymbolTableError != null) { assertEquals("There should be one error.", 1, stErrors.length); assertEquals(expectedSymbolTableError, stErrors[0].getType()); } else { assertEquals("Unexpected symbol table error(s): " + Joiner.on("\n").join(stErrors), 0, stErrors.length); } if (warning == null) { assertEquals( "Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings), 0, aggregateWarningCount); } else { assertEquals("There should be one warning, repeated " + numRepetitions + " time(s).", numRepetitions, aggregateWarningCount); for (int i = 0; i < numRepetitions; ++i) { JSError[] warnings = errorManagers[i].getWarnings(); JSError actual = warnings[0]; assertEquals(warning, actual.getType()); // Make sure that source information is always provided. if (!allowSourcelessWarnings) { assertTrue("Missing source file name in warning", actual.sourceName != null && !actual.sourceName.isEmpty()); assertTrue("Missing line number in warning", -1 != actual.lineNumber); assertTrue("Missing char number in warning", -1 != actual.get

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> compiler only; not accessed by compiler users. protected boolean analyzeChangedScopesOnly = true; // TODO(nicksantos): Decide if all of these are really necessary. // Many of them are just accessors that should be passed to the // CompilerPass's constructor. /** * Looks up an input (possibly an externs input) by input id. * May return null. */ public abstract CompilerInput getInput(InputId inputId); /** * Looks up a source file by name. May return null. */ abstract SourceFile getSourceFileByName(String sourceName); /** * Creates a new externs file. * @param name A name for the new externs file. * @throws IllegalArgumentException If the name of the externs file conflicts * with a pre-existing externs file. */ abstract CompilerInput newExternInput(String name); /** * Gets the module graph. May return null if there aren't at least two * modules. */ abstract JSModuleGraph getModuleGraph(); /** * Gets the inputs in the order in which they are being processed. * Only for use by {@code AbstractCompilerRunner}. */ abstract List<CompilerInput> getInputsInOrder(); /** * Gets a central registry of type information from the compiled JS. */ public abstract JSTypeRegistry getTypeRegistry(); /** * Gets a memoized scope creator with type information. */ abstract ScopeCreator getTypedScopeCreator(); /** * Gets the top scope. */ public abstract Scope getTopScope(); /** * Report an error or warning. */ public abstract void report(JSError error); /** * Report an internal error. */ abstract void throwInternalError(String msg, Exception cause); /** * Gets the current coding convention. */ public abstract CodingConvention getCodingConvention(); /** * Report code changes. * * Passes should call reportCodeChange when they alter the JS tree. This is * verified by CompilerTestCase. This allows us to optimize to a fixed point. */ public abstract void reportCodeChange(); /** * Logs a message under a central logger. */ abstract void addToDebugLog(String message); /** * Sets the CssRenamingMap. */ abstract void setCssRenamingMap(

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; /** * Tests for {@link PeepholeSubstituteAlternateSyntax} in isolation. * Tests for the interaction of multiple peephole passes are in * PeepholeIntegrationTest. */ public class PeepholeSubstituteAlternateSyntaxTest extends CompilerTestCase { // Externs for built-in constructors // Needed for testFoldLiteralObjectConstructors(), // testFoldLiteralArrayConstructors() and testFoldRegExp...() private static final String FOLD_CONSTANTS_TEST_EXTERNS = "var Object = function f(){};\n" + "var RegExp = function f(a){};\n" + "var Array = function f(a){};\n"; private boolean late = true; // TODO(user): Remove this when we no longer need to do string comparison. private PeepholeSubstituteAlternateSyntaxTest(boolean compareAsTree) { super(FOLD_CONSTANTS_TEST_EXTERNS, compareAsTree); } public PeepholeSubstituteAlternateSyntaxTest() { super(FOLD_CONSTANTS_TEST_EXTERNS); } @Override public void setUp() throws Exception { late = true; super.setUp(); enableLineNumberCheck(true); disableNormalize(); } @Override public CompilerPass getProcessor(final Compiler compiler) { PeepholeOptimizationsPass peepholePass = new PeepholeOptimizationsPass( compiler, new PeepholeSubstituteAlternateSyntax(late)); peepholePass.setRetraverseOnChange(false);

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>FoldReturnResult() { foldSame("function f(){return !1;}"); foldSame("function f(){return null;}"); fold("function f(){return void 0;}", "function f(){return}"); foldSame("function f(){return void foo();}"); fold("function f(){return undefined;}", "function f(){return}"); fold("function f(){if(a()){return undefined;}}", "function f(){if(a()){return}}"); } public void testUndefined() { foldSame("var x = undefined"); foldSame("function f(f) {var undefined=2;var x = undefined;}"); this.enableNormalize(); fold("var x = undefined", "var x=void 0"); foldSame( "var undefined = 1;" + "function f() {var undefined=2;var x = undefined;}"); foldSame("function f(undefined) {}"); foldSame("try {} catch(undefined) {}"); foldSame("for (undefined in {}) {}"); foldSame("undefined++;"); fold("undefined += undefined;", "undefined += void 0;"); } public void testSplitCommaExpressions() { late = false; // Don't try to split in expressions. foldSame("while (foo(), !0) boo()"); foldSame("var a = (foo(), !0);"); foldSame("a = (foo(), !0);"); // Don't try to split COMMA under LABELs. foldSame("a:a(),b()"); fold("(x=2), foo()", "x=2; foo()"); fold("foo(), boo();", "foo(); boo()"); fold("(a(), b()), (c(), d());", "a(); b(); (c(), d());"); fold("a(); b(); (c(), d());", "a(); b(); c(); d();"); fold("foo(), true", "foo();true"); foldSame("foo();true"); fold("function x(){foo(), !0}", "function x(){foo(); !0}"); foldSame("function x(){foo(); !0}"); } public void testComma1() { late = false; fold("1, 2", "1; 2"); late = true; foldSame("1,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>2"); } public void testComma2() { late = false; test("1, a()", "1; a()"); late = true; foldSame("1, a()"); } public void testComma3() { late = false; test("1, a(), b()", "1; a(); b()"); late = true; foldSame("1, a(), b()"); } public void testComma4() { late = false; test("a(), b()", "a();b()"); late = true; foldSame("a(), b()"); } public void testComma5() { late = false; test("a(), b(), 1", "a();b();1"); late = true; foldSame("a(), b(), 1"); } public void testStringArraySplitting() { testSame("var x=['1','2','3','4']"); testSame("var x=['1','2','3','4','5']"); test("var x=['1','2','3','4','5','6']", "var x='123456'.split('')"); test("var x=['1','2','3','4','5','00']", "var x='1 2 3 4 5 00'.split(' ')"); test("var x=['1','2','3','4','5','6','7']", "var x='1234567'.split('')"); test("var x=['1','2','3','4','5','6','00']", "var x='1 2 3 4 5 6 00'.split(' ')"); test("var x=[' ,',',',',',',',',',',']", "var x=' ,;,;,;,;,;,'.split(';')"); test("var x=[',,',' ',',',',',',',',']", "var x=',,; ;,;,;,;,'.split(';')"); test("var x=['a,',' ',',',',',',',',']", "var x='a,; ;,;,;,;,'.split(';')"); // all possible delimiters used, leave it alone testSame

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>( SourceMapGeneratorFactory.getInstance(SourceMapFormat.V3)); } }; abstract SourceMap getInstance(); } /** * Source maps can be very large different levels of detail can be specified. */ public static enum DetailLevel implements Predicate<Node> { // ALL is best when the fullest details are needed for debugging or for // code-origin analysis. ALL { @Override public boolean apply(Node node) { return true; } }, // SYMBOLS is intended to be used for stack trace deobfuscation when full // detail is not needed. SYMBOLS { @Override public boolean apply(Node node) { return node.isCall() || node.isNew() || node.isFunction() || node.isName() || NodeUtil.isGet(node) || NodeUtil.isObjectLitKey(node) || (node.isString() && NodeUtil.isGet(node.getParent())); } }; } public static class LocationMapping { final String prefix; final String replacement; public LocationMapping(String prefix, String replacement) { this.prefix = prefix; this.replacement = replacement; } } private final SourceMapGenerator generator; private List<LocationMapping> prefixMappings = Collections.emptyList(); private final Map<String, String> sourceLocationFixupCache = Maps.newHashMap(); private SourceMap(SourceMapGenerator generator) { this.generator = generator; } public void addMapping( Node node, FilePosition outputStartPosition, FilePosition outputEndPosition) { String sourceFile = node.getSourceFileName(); // If the node does not have an associated source file or // its line number is -1, then the node does not have sufficient // information for a mapping to be useful. if (sourceFile == null || node.getLineno() < 0) { return; } sourceFile = fixupSourceLocation(sourceFile); String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP); // Strangely, Rhino source lines are one based but columns are // zero based. // We don't change this for the v1 or v2 source maps but for // v3 we make them both 0 based

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> "undefined-var errors" on // variables that are defined in other JS files. t.traverseWithScope(scriptRoot, SyntacticScopeCreator.generateUntypedTopScope(compiler)); // TODO(bashir) Check if we need to createSynthesizedExternVar like process. } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isName()) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(parent.isFunction()); Preconditions.checkState(NodeUtil.isFunctionExpression(parent)); return; } // Check if this is a declaration for a var that has been declared // elsewhere. If so, mark it as a duplicate. if ((parent.isVar() || NodeUtil.isFunctionDeclaration(parent)) && varsToDeclareInExterns.contains(varName)) { createSynthesizedExternVar(varName); n.addSuppression("duplicate"); } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isFunctionExpression(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { // The extern checks are stricter, don't report a second error. if (!strictExternCheck || !t.getInput().isExtern()) { t.report(n, UNDEFINED_VAR_ERROR, varName); } if (sanityCheck) { throw new IllegalStateException("Unexpected variable " + varName); } else { createSynthesizedExternVar(varName); scope.getGlobalScope().declare(varName, n, null, compiler.getSynthesizedExternsInput()); } } return; } CompilerInput currInput = t.getInput(); CompilerInput varInput = var.input; if (currInput == varInput || currInput == null || varInput == null) { // The variable was defined in the same file. This is fine. return; } // Check module dependencies.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> JSModule currModule = currInput.getModule(); JSModule varModule = varInput.getModule(); JSModuleGraph moduleGraph = compiler.getModuleGraph(); if (!sanityCheck && varModule != currModule && varModule != null && currModule != null) { if (moduleGraph.dependsOn(currModule, varModule)) { // The module dependency was properly declared. } else { if (scope.isGlobal()) { if (moduleGraph.dependsOn(varModule, currModule)) { // The variable reference violates a declared module dependency. t.report(n, VIOLATED_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } else { // The variable reference is between two modules that have no // dependency relationship. This should probably be considered an // error, but just issue a warning for now. t.report(n, MISSING_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } else { t.report(n, STRICT_MODULE_DEP_ERROR, currModule.getName(), varModule.getName(), varName); } } } } /** * Create a new variable in a synthetic script. This will prevent * subsequent compiler passes from crashing. */ private void createSynthesizedExternVar(String varName) { Node nameNode = IR.name(varName); // Mark the variable as constant if it matches the coding convention // for constant vars. // NOTE(nicksantos): honestly, I'm not sure how much this matters. // AFAIK, all people who use the CONST coding convention also // compile with undeclaredVars as errors. We have some test // cases for this configuration though, and it makes them happier. if (compiler.getCodingConvention().isConstant(varName)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } getSynthesizedExternsRoot().addChildToBack( IR.var(nameNode)); varsToDeclareInExterns.remove(varName); compiler.reportCodeChange(); } /** * A check for name references in the externs inputs. These used to prevent * a variable from getting renamed

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>, but no longer have any effect. */ private class NameRefInExternsCheck extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { switch (parent.getType()) { case Token.VAR: case Token.FUNCTION: case Token.PARAM_LIST: // These are okay. break; case Token.GETPROP: if (n == parent.getFirstChild()) { Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString()); varsToDeclareInExterns.add(n.getString()); } } break; default: t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString()); Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { varsToDeclareInExterns.add(n.getString()); } break; } } } } /** Lazily create a "new" externs root for undeclared variables. */ private Node getSynthesizedExternsRoot() { return compiler.getSynthesizedExternsInput().getAstRoot(compiler); } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> null; } key = n.getQualifiedName(); /* * If it is not a simple variable and doesn't use this, then we assume * global variable. */ Node base = getBase(n); if (base != null && base.isThis()) { if (base.getJSType().isUnknownType()) { // Handle anonymous function created in constructor: // // /** // * @extends {goog.SubDisposable} // * @constructor */ // speel.Person = function() { // this.run = function() { // this.eh = new goog.events.EventHandler(); // } //}; key = t.getScope().getParentScope().getTypeOfThis().toString() + "~" + key; } else { if (n.getFirstChild() == null) { key = base.getJSType().toString() + "=" + key; } else { ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); if (objectType == null) { return null; } ObjectType hObjT = objectType; String propertyName = n.getLastChild().getString(); while (objectType != null) { hObjT = objectType; objectType = objectType.getImplicitPrototype(); if (objectType == null) { break; } if (objectType.getDisplayName().endsWith("prototype")) { continue; } if (!objectType.getPropertyNames().contains(propertyName)) { break; } } key = hObjT.toString() + "=" + key; } } } } return key; } @Override public void process(Node externs, Node root) { // This pass should not have gotten added in this case Preconditions.checkArgument(checkingPolicy != DisposalCheckingPolicy.OFF); // Initialize types googDisposableInterfaceType = compiler.getTypeRegistry().getType(DISPOSABLE_INTERFACE_TYPE_NAME); googEventsEventHandlerType = compiler.getTypeRegistry() .getType(EVENT_HANDLER_TYPE_NAME); /* * Required types not found therefore the kind of pattern considered * will not be found. */ if (googEventsEventHandlerType == null || googDisposableInterfaceType == null

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> true; } } isConstructorStack.push(isConstructor); isDisposalStack.push(isInDisposal); } else { isConstructorStack.push(inConstructorScope()); isDisposalStack.push(inDisposalScope()); } } @Override public void exitScope(NodeTraversal t) { isConstructorStack.pop(); isDisposalStack.pop(); } /* * Is the current node a call to goog.events.unlisten */ private void isGoogEventsUnlisten(Node n) { Preconditions.checkArgument(n.getChildCount() > 3); Node listener = n.getChildAtIndex(3); Node objectWithListener = n.getChildAtIndex(1); if (!objectWithListener.isQualifiedName()) { return; } if (listener.isFunction()) { /* * Anonymous function */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } else if (listener.isCall()) { if (!listener.getFirstChild().isQualifiedName()) { /* * Anonymous function */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } else if (listener.getFirstChild().getQualifiedName() .equals("goog.bind")) { /* * Using goog.bind to unlisten */ compiler.report(JSError.make(n.getSourceFileName(), n, UNLISTEN_WITH_ANONBOUND)); } } } private void visitCall(NodeTraversal t, Node n) { Node functionCalled = n.getFirstChild(); if (functionCalled == null || !functionCalled.isQualifiedName()) { return; } String functionCalledName = functionCalled.getQualifiedName(); JSType typeOfThis = getTypeOfThisForScope(t); if (typeOfThis == null) { return; } /* * Class considered eventful if there is an unlisten call in the * disposal. */ if (functionCalledName.equals("goog.events.unlisten")) { if (inDisposalScope()) { eventfulTypes.add(typeOfThis); } isGoogEventsUn

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>listen(n); } if (inDisposalScope() && functionCalledName.equals("goog.events.removeAll")) { eventfulTypes.add(typeOfThis); } /* * If member with qualified name gets disposed of when this class * gets disposed, consider the member type as an eventizer of this * class. */ JSType disposedType = maybeReturnDisposedType(n, inDisposalScope()); if (!collectorFilterType(disposedType)) { addEventize(getTypeOfThisForScope(t), disposedType); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: visitCall(t, n); break; default: break; } } } private class Traversal extends AbstractPostOrderCallback implements ScopedCallback { /* * Checks if the input node correspond to the creation of an eventful object */ private boolean createsEventfulObject(Node n) { Node first = n.getFirstChild(); JSType type = n.getJSType(); if (first == null || !first.isQualifiedName() || type.isEmptyType() || type.isUnknownType()) { return false; } boolean isOfTypeNeedingDisposal = false; for (JSType disposableType : eventfulTypes) { if (type.isSubtype(disposableType)) { isOfTypeNeedingDisposal = true; break; } } return isOfTypeNeedingDisposal; } /* * This function traverses the current scope to see if a locally * defined eventful object is assigned to a live-out variable. * * Note: This function could be called multiple times to traverse * the same scope if multiple local eventful objects are created in the * scope. */ private Node localEventfulObjectAssign( NodeTraversal t, Node propertyNode) { Node parent; if (!t.getScope().isGlobal()) { /* * In function */ parent = NodeUtil.getFunctionBody(t.getScopeRoot()); } else { /* * In global scope */ parent = t.getScopeRoot().getFirstChild(); } /*

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> * Check to see if locally created EventHandler is assigned to field */ for (Node sibling : parent.children()) { if (sibling.isExprResult()) { Node assign = sibling.getFirstChild(); if (assign.isAssign()) { // assign.getLastChild().isEquivalentTo(propertyNode) did not work if (propertyNode.getQualifiedName().equals(assign.getLastChild() .getQualifiedName())) { if (!assign.getFirstChild().isName()) { return assign.getFirstChild(); } } } } } /* * Eventful object created and assigned to a local variable which is not * assigned to another variable in a way to allow disposal. */ String key = generateKey(t, propertyNode, false); if (key == null) { return null; } EventfulObjectState e; if (eventfulObjectMap.containsKey(key)) { e = eventfulObjectMap.get(key); if (e.seen == SeenType.ALLOCATED) { e.seen = SeenType.ALLOCATED_LOCALLY; } } else { e = new EventfulObjectState(); e.seen = SeenType.ALLOCATED_LOCALLY; eventfulObjectMap.put(key, e); } e.allocationSite = propertyNode; return null; } /* * Record the creation of a new eventful object. */ private void visitNew(NodeTraversal t, Node n, Node parent) { if (!createsEventfulObject(n)) { return; } /* * Insert allocation site and construct into eventfulObjectMap */ String key; Node propertyNode; /* * Handles (E is an eventful class): * - object.something = new E(); * - local = new E(); * - var local = new E(); */ if (parent.isAssign()) { propertyNode = parent.getFirstChild(); } else { propertyNode = parent; } key = generateKey(t, propertyNode, false); if (key == null) { return; } EventfulObjectState e; if (eventfulObjectMap.containsKey(key)) { e = eventfulObjectMap.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> } } } } private List<Node> maybeGetValueNodesFromCall(Node n) { List<Node> ret = Lists.newArrayList(); Node first = n.getFirstChild(); if (first == null || !first.isQualifiedName()) { return ret; } String property = first.getQualifiedName(); Node base = first.getFirstChild(); JSType baseType = null; if (base != null) { baseType = base.getJSType(); } for (JSType key : disposeCalls.keySet()) { if (key == null || (baseType != null && isPossiblySubtype(baseType, key))) { addDisposeArgumentsMatched(disposeCalls.get(key), first, property, ret); } } return ret; } /* * Look for calls to an eventful object's disposal functions. * (dispose or removeAll will remove all event listeners from * an EventHandler). */ private void visitCall(NodeTraversal t, Node n) { // Filter the calls to find a "dispose" call List<Node> variableNodes = maybeGetValueNodesFromCall(n); for (Node variableNode : variableNodes) { Preconditions.checkState(variableNode != null); // Only consider removals on eventful object boolean isTrackedRemoval = false; JSType vnType = variableNode.getJSType(); for (JSType type : eventfulTypes) { if (isPossiblySubtype(vnType, type)) { isTrackedRemoval = true; } } if (!isTrackedRemoval) { continue; } String key = generateKey(t, variableNode, false); if (key == null) { continue; } eventfulObjectDisposed(t, variableNode); } } /** * Dereference a type, autoboxing it and filtering out null. * From {@link CheckAccessControls} */ private JSType dereference(JSType type) { return type == null ? null : type.dereference(); } /* * Check function definitions to add custom dispose methods. */ public void visitFunction(NodeTraversal t, Node n) { Preconditions.checkArgument(n.isFunction()); JSDocInfo jsDocInfo = Node

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>FlowGraph(); LiveVariablesAnalysis liveness = new LiveVariablesAnalysis(cfg, t.getScope(), compiler); liveness.analyze(); for (Var v : liveness.getEscapedLocals()) { eventfulObjectDisposed(t, v.getNode()); } } @Override public void exitScope(NodeTraversal t) { } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.ASSIGN: visitAssign(t, n); break; case Token.CALL: visitCall(t, n); break; case Token.FUNCTION: visitFunction(t, n); break; case Token.NEW: visitNew(t, n, parent); break; case Token.RETURN: visitReturn(t, n); break; default: break; } } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.keySet(); } @Override public Scope getScope(Var var) { return var.scope; } /** * Gets the reference collection for the given variable. */ @Override public ReferenceCollection getReferences(Var v) { return referenceMap.get(v); } /** * For each node, update the block stack and reference collection * as appropriate. */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { Var v; if (n.getString().equals("arguments")) { v = t.getScope().getArgumentsVar(); } else { v = t.getScope().getVar(n.getString()); } if (v != null && varFilter.apply(v)) { addReference(v, new Reference(n, t, blockStack.peek())); } } if (isBlockBoundary(n, parent)) { blockStack.pop(); } } /** * Updates block stack and invokes any additional behavior. */ @Override public void enterScope(NodeTraversal t) { Node n = t.getScope().getRootNode(); BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek(); blockStack.push(new BasicBlock(parent, n)); } /** * Updates block stack and invokes any additional behavior. */ @Override public void exitScope(NodeTraversal t) { blockStack.pop(); if (t.getScope().isGlobal()) { // Update global scope reference lists when we are done with it. compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot()); behavior.afterExitScope(t, compiler.getGlobalVarReferences()); } else { behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap)); } } /** * Updates block stack. */ @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // If node is a new basic block, put on basic block stack if (isBlockBoundary(n, parent)) { blockStack.push(new BasicBlock(blockStack.peek(), n)); } return true; } /** * @return true if

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> this node marks the start of a new basic block */ private static boolean isBlockBoundary(Node n, Node parent) { if (parent != null) { switch (parent.getType()) { case Token.DO: case Token.FOR: case Token.TRY: case Token.WHILE: case Token.WITH: // NOTE: TRY has up to 3 child blocks: // TRY // BLOCK // BLOCK // CATCH // BLOCK // Note that there is an explicit CATCH token but no explicit // FINALLY token. For simplicity, we consider each BLOCK // a separate basic BLOCK. return true; case Token.AND: case Token.HOOK: case Token.IF: case Token.OR: // The first child of a conditional is not a boundary, // but all the rest of the children are. return n != parent.getFirstChild(); } } return n.isCase(); } private void addReference(Var v, Reference reference) { // Create collection if none already ReferenceCollection referenceInfo = referenceMap.get(v); if (referenceInfo == null) { referenceInfo = new ReferenceCollection(); referenceMap.put(v, referenceInfo); } // Add this particular reference referenceInfo.add(reference); } interface ReferenceMap { ReferenceCollection getReferences(Var var); } private static class ReferenceMapWrapper implements ReferenceMap { private final Map<Var, ReferenceCollection> referenceMap; public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) { this.referenceMap = referenceMap; } @Override public ReferenceCollection getReferences(Var var) { return referenceMap.get(var); } } /** * Way for callers to add specific behavior during traversal that * utilizes the built-up reference information. */ interface Behavior { /** * Called after we finish with a scope. */ void afterExitScope(NodeTraversal t, ReferenceMap referenceMap); } static final Behavior DO_NOTHING_BEHAVIOR = new Behavior() { @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {} }; /** * A collection of references. Can be subclassed to apply checks or

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> // Specifically, var declarations without assignments such as "var a;" // are not. return true; } return false; } /** * @param index The index into the references array to look for an * initialized assignment reference. That is, an assignment immediately * follow a variable declaration that itself does not initialize the * variable. */ private boolean isInitializingAssignmentAt(int index) { if (index < references.size() && index > 0) { Reference maybeDecl = references.get(index - 1); if (maybeDecl.isVarDeclaration()) { Preconditions.checkState(!maybeDecl.isInitializingDeclaration()); Reference maybeInit = references.get(index); if (maybeInit.isSimpleAssignmentToName()) { return true; } } } return false; } /** * @return The reference that provides the value for the variable at the * time of the first read, if known, otherwise null. * * This is either the variable declaration ("var a = ...") or first * reference following the declaration if it is an assignment. */ Reference getInitializingReference() { if (isInitializingDeclarationAt(0)) { return references.get(0); } else if (isInitializingAssignmentAt(1)) { return references.get(1); } return null; } /** * Constants are allowed to be defined after their first use. */ Reference getInitializingReferenceForConstants() { int size = references.size(); for (int i = 0; i < size; i++) { if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) { return references.get(i); } } return null; } /** * @return Whether the variable is only assigned a value once for its * lifetime. */ boolean isAssignedOnceInLifetime() { Reference ref = getOneAndOnlyAssignment(); if (ref == null) { return false; } // Make sure this assignment is not in a loop. for (BasicBlock block = ref.getBasicBlock(); block != null; block = block.getParent()) { if (block.isFunction) { if (ref.getSymbol().getScope() != ref.scope) { return

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.getScope(), t.getInput().getInputId()); } /** * Creates a variable reference in a given script file name, used in tests. * * @return The created reference. */ @VisibleForTesting static Reference createRefForTest(CompilerInput input) { return new Reference(new Node(Token.NAME), null, null, input.getInputId()); } private Reference(Node nameNode, BasicBlock basicBlock, Scope scope, InputId inputId) { this.nameNode = nameNode; this.basicBlock = basicBlock; this.scope = scope; this.inputId = inputId; this.sourceFile = nameNode.getStaticSourceFile(); } /** * Makes a copy of the current reference using a new Scope instance. */ Reference cloneWithNewScope(Scope newScope) { return new Reference(nameNode, basicBlock, newScope, inputId); } @Override public Var getSymbol() { return scope.getVar(nameNode.getString()); } @Override public Node getNode() { return nameNode; } public InputId getInputId() { return inputId; } @Override public StaticSourceFile getSourceFile() { return sourceFile; } boolean isDeclaration() { Node parent = getParent(); Node grandparent = parent.getParent(); return DECLARATION_PARENTS.contains(parent.getType()) || parent.isParamList() && grandparent.isFunction(); } boolean isVarDeclaration() { return getParent().isVar(); } boolean isHoistedFunction() { return NodeUtil.isHoistedFunctionDeclaration(getParent()); } /** * Determines whether the variable is initialized at the declaration. */ boolean isInitializingDeclaration() { // VAR is the only type of variable declaration that may not initialize // its variable. Catch blocks, named functions, and parameters all do. return isDeclaration() && !getParent().isVar() || nameNode.getFirstChild() != null; } /** * @return For an assignment, variable declaration, or function declaration * return the assigned value, otherwise null. */ Node getAssignedValue() { Node parent = getParent(); return (parent.isFunction()) ? parent : NodeUtil.getAssignedValue

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>(nameNode); } BasicBlock getBasicBlock() { return basicBlock; } Node getParent() { return getNode().getParent(); } Node getGrandparent() { Node parent = getParent(); return parent == null ? null : parent.getParent(); } private static boolean isLhsOfForInExpression(Node n) { Node parent = n.getParent(); if (parent.isVar()) { return isLhsOfForInExpression(parent); } return NodeUtil.isForIn(parent) && parent.getFirstChild() == n; } boolean isSimpleAssignmentToName() { Node parent = getParent(); return parent.isAssign() && parent.getFirstChild() == nameNode; } boolean isLvalue() { Node parent = getParent(); int parentType = parent.getType(); return (parentType == Token.VAR && nameNode.getFirstChild() != null) || parentType == Token.INC || parentType == Token.DEC || (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == nameNode) || isLhsOfForInExpression(nameNode); } Scope getScope() { return scope; } } /** * Represents a section of code that is uninterrupted by control structures * (conditional or iterative logic). */ static final class BasicBlock { private final BasicBlock parent; /** * Determines whether the block may not be part of the normal control flow, * but instead "hoisted" to the top of the scope. */ private final boolean isHoisted; /** * Whether this block denotes a function scope. */ private final boolean isFunction; /** * Whether this block denotes a loop. */ private final boolean isLoop; /** * Creates a new block. * @param parent The containing block. * @param root The root node of the block. */ BasicBlock(BasicBlock parent, Node root) { this.parent = parent; // only named functions may be hoisted. this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root); this.isFunction = root.isFunction(); if (root.getParent() != null) { int pType = root.getParent().getType

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>(); this.isLoop = pType == Token.DO || pType == Token.WHILE || pType == Token.FOR; } else { this.isLoop = false; } } BasicBlock getParent() { return parent; } /** * Determines whether this block is equivalent to the very first block that * is created when reference collection traversal enters global scope. Note * that when traversing a single script in a hot-swap fashion a new instance * of {@code BasicBlock} is created. * * @return true if this is global scope block. */ boolean isGlobalScopeBlock() { return getParent() == null; } /** * Determines whether this block is guaranteed to begin executing before * the given block does. */ boolean provablyExecutesBefore(BasicBlock thatBlock) { // If thatBlock is a descendant of this block, and there are no hoisted // blocks between them, then this block must start before thatBlock. BasicBlock currentBlock; for (currentBlock = thatBlock; currentBlock != null && currentBlock != this; currentBlock = currentBlock.getParent()) { if (currentBlock.isHoisted) { return false; } } if (currentBlock == this) { return true; } if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) { return true; } return false; } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> suggestion; this.distance = distance; } } public TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry, Scope topScope, MemoizedScopeCreator scopeCreator, CheckLevel reportMissingOverride) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.reverseInterpreter = reverseInterpreter; this.typeRegistry = typeRegistry; this.topScope = topScope; this.scopeCreator = scopeCreator; this.reportMissingOverride = reportMissingOverride; this.reportUnknownTypes = ((Compiler) compiler).getOptions().enables( DiagnosticGroups.REPORT_UNKNOWN_TYPES); this.inferJSDocInfo = new InferJSDocInfo(compiler); ClassLoader classLoader = TypeCheck.class.getClassLoader(); try { Class<?> c = classLoader.loadClass( "com.google.common.string.EditDistance"); editDistance = c.getDeclaredMethod( "getEditDistance", String.class, String.class, boolean.class); } catch (Exception ignored) { editDistance = null; } } public TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry, CheckLevel reportMissingOverride) { this(compiler, reverseInterpreter, typeRegistry, null, null, reportMissingOverride); } TypeCheck(AbstractCompiler compiler, ReverseAbstractInterpreter reverseInterpreter, JSTypeRegistry typeRegistry) { this(compiler, reverseInterpreter, typeRegistry, null, null, CheckLevel.WARNING); } /** Turn on the missing property check. Returns this for easy chaining. */ TypeCheck reportMissingProperties(boolean report) { reportMissingProperties = report; return this; } /** * Main entry point for this phase of processing. This follows the pattern for * JSCompiler phases. * * @param externsRoot The root of the externs parse tree. * @param jsRoot The root of the input parse tree to be checked. */ @Override public void process(Node externsRoot, Node jsRoot) { Preconditions.checkNotNull(scopeCreator); Preconditions.checkNotNull(topScope); Node externsAndJs = jsRoot.getParent(); Preconditions.checkState(externsAndJs !=

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> null); Preconditions.checkState( externsRoot == null || externsAndJs.hasChild(externsRoot)); if (externsRoot != null) { check(externsRoot, true); } check(jsRoot, false); } /** Main entry point of this phase for testing code. */ public Scope processForTesting(Node externsRoot, Node jsRoot) { Preconditions.checkState(scopeCreator == null); Preconditions.checkState(topScope == null); Preconditions.checkState(jsRoot.getParent() != null); Node externsAndJsRoot = jsRoot.getParent(); scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler)); topScope = scopeCreator.createScope(externsAndJsRoot, null); TypeInferencePass inference = new TypeInferencePass(compiler, reverseInterpreter, topScope, scopeCreator); inference.process(externsRoot, jsRoot); process(externsRoot, jsRoot); return topScope; } public void check(Node node, boolean externs) { Preconditions.checkNotNull(node); NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator); inExterns = externs; t.traverseWithScope(node, topScope); if (externs) { inferJSDocInfo.process(node, null); } else { inferJSDocInfo.process(null, node); } } private void checkNoTypeCheckSection(Node n, boolean enterSection) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.VAR: case Token.FUNCTION: case Token.ASSIGN: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { if (enterSection) { noTypeCheckSection++; } else { noTypeCheckSection--; } } validator.setShouldReport(noTypeCheckSection == 0); break; } } private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType, String... arguments) { if (noTypeCheckSection == 0) { t.report(n, diagnosticType, arguments); } } @

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Override public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { checkNoTypeCheckSection(n, true); switch (n.getType()) { case Token.FUNCTION: // normal type checking final Scope outerScope = t.getScope(); final String functionPrivateName = n.getFirstChild().getString(); if (functionPrivateName != null && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false) && // Ideally, we would want to check whether the type in the scope // differs from the type being defined, but then the extern // redeclarations of built-in types generates spurious warnings. !(outerScope.getVar( functionPrivateName).getType() instanceof FunctionType)) { report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName); } // TODO(user): Only traverse the function's body. The function's // name and arguments are traversed by the scope creator, and ideally // should not be traversed by the type checker. break; } return true; } /** * This is the meat of the type checking. It is basically one big switch, * with each case representing one type of parse tree node. The individual * cases are usually pretty straightforward. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. */ @Override public void visit(NodeTraversal t, Node n, Node parent) { JSType childType; JSType leftType, rightType; Node left, right; // To be explicitly set to false if the node is not typeable. boolean typeable = true; switch (n.getType()) { case Token.CAST: Node expr = n.getFirstChild(); JSType exprType = getJSType(expr); JSType castType = getJSType(n); // TODO(johnlenz): determine if we can limit object literals in some // way. if (!expr.isObjectLit()) { validator.expectCanCast(t, n, castType, exprType);

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> } ensureTyped(t, n, castType); if (castType.isSubtype(exprType) || expr.isObjectLit()) { expr.setJSType(castType); } break; case Token.NAME: typeable = visitName(t, n, parent); break; case Token.PARAM_LIST: typeable = false; break; case Token.COMMA: ensureTyped(t, n, getJSType(n.getLastChild())); break; case Token.TRUE: case Token.FALSE: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.THIS: ensureTyped(t, n, t.getScope().getTypeOfThis()); break; case Token.NULL: ensureTyped(t, n, NULL_TYPE); break; case Token.NUMBER: ensureTyped(t, n, NUMBER_TYPE); break; case Token.STRING: ensureTyped(t, n, STRING_TYPE); break; case Token.STRING_KEY: typeable = false; break; case Token.GETTER_DEF: case Token.SETTER_DEF: // Object literal keys are handled with OBJECTLIT break; case Token.ARRAYLIT: ensureTyped(t, n, ARRAY_TYPE); break; case Token.REGEXP: ensureTyped(t, n, REGEXP_TYPE); break; case Token.GETPROP: visitGetProp(t, n, parent); typeable = !(parent.isAssign() && parent.getFirstChild() == n); break; case Token.GETELEM: visitGetElem(t, n); // The type of GETELEM is always unknown, so no point counting that. // If that unknown leaks elsewhere (say by an assignment to another // variable), then it will be counted. typeable = false; break; case Token.VAR: visitVar(t, n); typeable = false; break; case Token.NEW: visitNew(t, n); break; case Token.CALL: visitCall(t, n); typeable = !parent.isExprResult(); break; case Token.RETURN: visitReturn(t, n

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.IN: left = n.getFirstChild(); right = n.getLastChild(); rightType = getJSType(right); validator.expectString(t, left, getJSType(left), "left side of 'in'"); validator.expectObject(t, n, rightType, "'in' requires an object"); if (rightType.isStruct()) { report(t, right, IN_USED_WITH_STRUCT); } ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.INSTANCEOF: left = n.getFirstChild(); right = n.getLastChild(); rightType = getJSType(right).restrictByNotNullOrUndefined(); validator.expectAnyObject( t, left, getJSType(left), "deterministic instanceof yields false"); validator.expectActualObject( t, right, rightType, "instanceof requires an object"); ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.ASSIGN: visitAssign(t, n); typeable = false; break; case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_SUB: case Token.ASSIGN_ADD: case Token.ASSIGN_MUL: checkPropCreation(t, n.getFirstChild()); // fall through case Token.LSH: case Token.RSH: case Token.URSH: case Token.DIV: case Token.MOD: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.SUB: case Token.ADD: case Token.MUL: visitBinaryOperator(n.getType(), t, n); break; case Token.DELPROP: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.CASE: JSType switchType = getJSType(parent.getFirstChild()); JSType caseType = getJSType(n.getFirstChild()); validator.expectSwitchMatchesCase(t, n, switchType,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> caseType); typeable = false; break; case Token.WITH: { Node child = n.getFirstChild(); childType = getJSType(child); validator.expectObject(t, child, childType, "with requires an object"); typeable = false; break; } case Token.FUNCTION: visitFunction(t, n); break; // These nodes have no interesting type behavior. case Token.LABEL: case Token.LABEL_NAME: case Token.SWITCH: case Token.BREAK: case Token.CATCH: case Token.TRY: case Token.SCRIPT: case Token.EXPR_RESULT: case Token.BLOCK: case Token.EMPTY: case Token.DEFAULT_CASE: case Token.CONTINUE: case Token.DEBUGGER: case Token.THROW: typeable = false; break; // These nodes require data flow analysis. case Token.DO: case Token.IF: case Token.WHILE: typeable = false; break; case Token.FOR: if (NodeUtil.isForIn(n)) { Node obj = n.getChildAtIndex(1); if (getJSType(obj).isStruct()) { report(t, obj, IN_USED_WITH_STRUCT); } } typeable = false; break; // These nodes are typed during the type inference. case Token.AND: case Token.HOOK: case Token.OBJECTLIT: case Token.OR: if (n.getJSType() != null) { // If we didn't run type inference. ensureTyped(t, n); } else { // If this is an enum, then give that type to the objectlit as well. if ((n.isObjectLit()) && (parent.getJSType() instanceof EnumType)) { ensureTyped(t, n, parent.getJSType()); } else { ensureTyped(t, n); } } if (n.isObjectLit()) { JSType typ = getJSType(n); for (Node key : n.children()) { visitObjLitKey(t, key, n, typ); } } break; default: report(t, n, UNEXPECTED_

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> } else if (!foundInterfaceProperty && !superClassHasProperty && !superInterfaceHasProperty) { // there is no superclass nor interface implementation compiler.report( t.makeError(n, UNKNOWN_OVERRIDE, propertyName, ctorType.getInstanceType().toString())); } } /** * Given a constructor or an interface type, find out whether the unknown * type is a supertype of the current type. */ private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) { Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface()); Preconditions.checkArgument(!ctor.isUnknownType()); // The type system should notice inheritance cycles on its own // and break the cycle. while (true) { ObjectType maybeSuperInstanceType = ctor.getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return false; } if (maybeSuperInstanceType.isUnknownType() || maybeSuperInstanceType.isEmptyType()) { return true; } ctor = maybeSuperInstanceType.getConstructor(); if (ctor == null) { return false; } Preconditions.checkState(ctor.isConstructor() || ctor.isInterface()); } } /** * Visits an ASSIGN node for cases such as * <pre> * interface.property2.property = ...; * </pre> */ private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object, String property, Node lvalue, Node rvalue) { JSType rvalueType = getJSType(rvalue); // Only 2 values are allowed for methods: // goog.abstractMethod // function () {}; // or for properties, no assignment such as: // InterfaceFoo.prototype.foobar; String abstractMethodName = compiler.getCodingConvention().getAbstractMethodName(); if (!rvalueType.isFunctionType()) { // This is bad i18n style but we don't localize our compiler errors. String abstractMethodMessage = (abstractMethodName != null) ? ", or " + abstractMethodName : ""; compiler.report( t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION, abstractMethodMessage)); } if (assign.getLastChild().isFunction()

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) { compiler.report( t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY, abstractMethodName)); } } /** * Visits a NAME node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. * @return whether the node is typeable or not */ boolean visitName(NodeTraversal t, Node n, Node parent) { // At this stage, we need to determine whether this is a leaf // node in an expression (which therefore needs to have a type // assigned for it) versus some other decorative node that we // can safely ignore. Function names, arguments (children of LP nodes) and // variable declarations are ignored. // TODO(user): remove this short-circuiting in favor of a // pre order traversal of the FUNCTION, CATCH, LP and VAR nodes. int parentNodeType = parent.getType(); if (parentNodeType == Token.FUNCTION || parentNodeType == Token.CATCH || parentNodeType == Token.PARAM_LIST || parentNodeType == Token.VAR) { return false; } JSType type = n.getJSType(); if (type == null) { type = getNativeType(UNKNOWN_TYPE); Var var = t.getScope().getVar(n.getString()); if (var != null) { JSType varType = var.getType(); if (varType != null) { type = varType; } } } ensureTyped(t, n, type); return true; } /** * Visits a GETPROP node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of <code>n</code> */ private void visitGetProp(NodeTraversal t, Node n, Node parent) { // obj.prop or obj.method() // Lots of types can appear on the left

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> (pair.distance == shortest) { if (bestSoFar != null && pair.suggestion.compareToIgnoreCase(bestSoFar) > 0) { continue; } } shortest = pair.distance; bestSoFar = pair.suggestion; } } } } if (bestSoFar != null) { return new SuggestionPair(bestSoFar, shortest); } return null; } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().isOr() && parent.getParent().getFirstChild() == parent; case Token.CAST: return isPropertyTest(parent); } return false; } /** * Visits a GETELEM node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitGetElem(NodeTraversal t, Node n) { validator.expectIndexMatch( t, n, getJSType(n.getFirstChild()), getJSType(n.getLastChild())); ensureTyped(t, n); } /** * Visits a VAR node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> case Token.DIV: case Token.MOD: case Token.MUL: case Token.SUB: validator.expectNumber(t, left, leftType, "left operand"); validator.expectNumber(t, right, rightType, "right operand"); break; case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITOR: case Token.BITAND: case Token.BITXOR: case Token.BITOR: validator.expectBitwiseable(t, left, leftType, "bad left operand to bitwise operator"); validator.expectBitwiseable(t, right, rightType, "bad right operand to bitwise operator"); break; case Token.ASSIGN_ADD: case Token.ADD: break; default: report(t, n, UNEXPECTED_TOKEN, Token.name(op)); } ensureTyped(t, n); } /** * <p>Checks enum aliases. * * <p>We verify that the enum element type of the enum used * for initialization is a subtype of the enum element type of * the enum the value is being copied in.</p> * * <p>Example:</p> * <pre>var myEnum = myOtherEnum;</pre> * * <p>Enum aliases are irregular, so we need special code for this :(</p> * * @param value the value used for initialization of the enum */ private void checkEnumAlias( NodeTraversal t, JSDocInfo declInfo, Node value) { if (declInfo == null || !declInfo.hasEnumParameterType()) { return; } JSType valueType = getJSType(value); if (!valueType.isEnumType()) { return; } EnumType valueEnumType = valueType.toMaybeEnumType(); JSType valueEnumPrimitiveType = valueEnumType.getElementsType().getPrimitiveType(); validator.expectCanAssignTo(t, value, valueEnumPrimitiveType, declInfo.getEnumParameterType().evaluate(t.getScope(), typeRegistry), "incompatible enum element types"); } /** * This method gets the JSType from the Node argument and verifies that it is * present.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>rightContext", "global", "ignoreCase", "lastIndex", "multiline", "source"); private final AbstractCompiler compiler; private boolean globalRegExpPropertiesUsed = false; public boolean isGlobalRegExpPropertiesUsed() { return globalRegExpPropertiesUsed; } public CheckRegExp(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isReferenceName(n)) { String name = n.getString(); if (name.equals("RegExp") && t.getScope().getVar(name) == null) { int parentType = parent.getType(); boolean first = (n == parent.getFirstChild()); if (!((parentType == Token.NEW && first) || (parentType == Token.CALL && first) || (parentType == Token.INSTANCEOF && !first) || parentType == Token.EQ || parentType == Token.NE || parentType == Token.SHEQ || parentType == Token.SHNE || parentType == Token.CASE || (parentType == Token.GETPROP && first && !REGEXP_PROPERTY_BLACKLIST.contains( parent.getLastChild().getString())))) { t.report(n, REGEXP_REFERENCE); globalRegExpPropertiesUsed = true; } } // Check the syntax of regular expression patterns. } else if (n.isRegExp()) { String pattern = n.getFirstChild().getString(); String flags = n.getChildCount() == 2 ? n.getLastChild().getString() : ""; try { RegExpTree.parseRegExp(pattern, flags); } catch (IllegalArgumentException ex) { t.report(n, MALFORMED_REGEXP, ex.getMessage()); } catch (IndexOutOfBoundsException ex) { t.report(n, MALFORMED_REGEXP, ex.getMessage()); } } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.hasType()) { boolean valid = false; switch (node.getType()) { // Casts are valid case com.google.javascript.rhino.head.Token.LP: valid = node instanceof ParenthesizedExpression; break; // Variable declarations are valid case com.google.javascript.rhino.head.Token.VAR: valid = true; break; // Function declarations are valid case com.google.javascript.rhino.head.Token.FUNCTION: FunctionNode fnNode = (FunctionNode) node; valid = fnNode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT; break; // Object literal properties, catch declarations and variable // initializers are valid. case com.google.javascript.rhino.head.Token.NAME: AstNode parent = node.getParent(); valid = parent instanceof ObjectProperty || parent instanceof CatchClause || parent instanceof FunctionNode || (parent instanceof VariableInitializer && node == ((VariableInitializer) parent).getTarget()); break; // Object literal properties are valid case com.google.javascript.rhino.head.Token.GET: case com.google.javascript.rhino.head.Token.SET: case com.google.javascript.rhino.head.Token.NUMBER: case com.google.javascript.rhino.head.Token.STRING: valid = node.getParent() instanceof ObjectProperty; break; // Property assignments are valid, if at the root of an expression. case com.google.javascript.rhino.head.Token.ASSIGN: if (node instanceof Assignment) { valid = isExprStmt(node.getParent()) && isPropAccess(((Assignment) node).getLeft()); } break; // Property definitions are valid, if at the root of an expression. case com.google.javascript.rhino.head.Token.GETPROP: case com.google.javascript.rhino.head.Token.GETELEM: valid = isExprStmt(node.getParent()); break; case com.google.javascript.rhino.head.Token.CALL: valid = info.isDefine(); break; } if (!valid) { errorReporter.warning(MISPLACED_TYPE_ANNOTATION, sourceName, node.get

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> { reportGetterParam(el.getLeft()); } } else if (el.isSetter()) { key.setType(Token.SETTER_DEF); Preconditions.checkState(value.isFunction()); if (!getFnParamNode(value).hasOneChild()) { reportSetterParam(el.getLeft()); } } key.addChildToFront(value); node.addChildToBack(key); } return node; } /** * @param fnNode The function. * @return The Node containing the Function parameters. */ Node getFnParamNode(Node fnNode) { // Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ] Preconditions.checkArgument(fnNode.isFunction()); return fnNode.getFirstChild().getNext(); } @Override Node processObjectProperty(ObjectProperty propertyNode) { return processInfixExpression(propertyNode); } @Override Node processParenthesizedExpression(ParenthesizedExpression exprNode) { Node node = transform(exprNode.getExpression()); return node; } @Override Node processPropertyGet(PropertyGet getNode) { Node leftChild = transform(getNode.getTarget()); AstNode nodeProp = getNode.getProperty(); Node rightChild = transformAsString(nodeProp); if (nodeProp instanceof Name && !isAllowedProp( ((Name) nodeProp).getIdentifier())) { errorReporter.warning(INVALID_ES3_PROP_NAME, sourceName, rightChild.getLineno(), "", rightChild.getCharno()); } Node newNode = newNode( Token.GETPROP, leftChild, rightChild); newNode.setLineno(leftChild.getLineno()); newNode.setCharno(leftChild.getCharno()); maybeSetLengthFrom(newNode, getNode); return newNode; } @Override Node processRegExpLiteral(RegExpLiteral literalNode) { Node literalStringNode = newStringNode(literalNode.getValue()); // assume it's on the same line. literalStringNode.setLineno(literalNode.getLineno()); maybeSetLengthFrom(literalStringNode, literalNode); Node node = newNode(Token.REGEXP, literalStringNode); String flags = literalNode.getFlags(); if (flags != null && !

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>) { switch (token) { case com.google.javascript.rhino.head.Token.RETURN: return Token.RETURN; case com.google.javascript.rhino.head.Token.BITOR: return Token.BITOR; case com.google.javascript.rhino.head.Token.BITXOR: return Token.BITXOR; case com.google.javascript.rhino.head.Token.BITAND: return Token.BITAND; case com.google.javascript.rhino.head.Token.EQ: return Token.EQ; case com.google.javascript.rhino.head.Token.NE: return Token.NE; case com.google.javascript.rhino.head.Token.LT: return Token.LT; case com.google.javascript.rhino.head.Token.LE: return Token.LE; case com.google.javascript.rhino.head.Token.GT: return Token.GT; case com.google.javascript.rhino.head.Token.GE: return Token.GE; case com.google.javascript.rhino.head.Token.LSH: return Token.LSH; case com.google.javascript.rhino.head.Token.RSH: return Token.RSH; case com.google.javascript.rhino.head.Token.URSH: return Token.URSH; case com.google.javascript.rhino.head.Token.ADD: return Token.ADD; case com.google.javascript.rhino.head.Token.SUB: return Token.SUB; case com.google.javascript.rhino.head.Token.MUL: return Token.MUL; case com.google.javascript.rhino.head.Token.DIV: return Token.DIV; case com.google.javascript.rhino.head.Token.MOD: return Token.MOD; case com.google.javascript.rhino.head.Token.NOT: return Token.NOT; case com.google.javascript.rhino.head.Token.BITNOT: return Token.BITNOT; case com.google.javascript.rhino.head.Token.POS: return Token.POS; case com.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.Token.OBJECTLIT: return Token.OBJECTLIT; case com.google.javascript.rhino.head.Token.TRY: return Token.TRY; // The LP represents a parameter list case com.google.javascript.rhino.head.Token.LP: return Token.PARAM_LIST; case com.google.javascript.rhino.head.Token.COMMA: return Token.COMMA; case com.google.javascript.rhino.head.Token.ASSIGN: return Token.ASSIGN; case com.google.javascript.rhino.head.Token.ASSIGN_BITOR: return Token.ASSIGN_BITOR; case com.google.javascript.rhino.head.Token.ASSIGN_BITXOR: return Token.ASSIGN_BITXOR; case com.google.javascript.rhino.head.Token.ASSIGN_BITAND: return Token.ASSIGN_BITAND; case com.google.javascript.rhino.head.Token.ASSIGN_LSH: return Token.ASSIGN_LSH; case com.google.javascript.rhino.head.Token.ASSIGN_RSH: return Token.ASSIGN_RSH; case com.google.javascript.rhino.head.Token.ASSIGN_URSH: return Token.ASSIGN_URSH; case com.google.javascript.rhino.head.Token.ASSIGN_ADD: return Token.ASSIGN_ADD; case com.google.javascript.rhino.head.Token.ASSIGN_SUB: return Token.ASSIGN_SUB; case com.google.javascript.rhino.head.Token.ASSIGN_MUL: return Token.ASSIGN_MUL; case com.google.javascript.rhino.head.Token.ASSIGN_DIV: return Token.ASSIGN_DIV; case com.google.javascript.rhino.head.Token.ASSIGN_MOD: return Token.ASSIGN_MOD; case com.google.javascript.rhino.head.Token.HOOK: return Token.HOOK; case com.google.javascript.rhino.head.Token.OR: return Token.OR; case com.google.javascript.rhino.head.Token.AND: return Token.AND; case com.google.javascript.rhino.head.Token.INC

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>().iterator().next()); } symbols.add(symbols.get(0)); return Joiner.on(" -> ").join(symbols); } public List<INPUT> getSortedList() { return Collections.<INPUT>unmodifiableList(sortedList); } /** * Gets all the dependencies of the given roots. The inputs must be returned * in a stable order. In other words, if A comes before B, and A does not * transitively depend on B, then A must also come before B in the returned * list. */ public List<INPUT> getSortedDependenciesOf(List<INPUT> roots) { return getDependenciesOf(roots, true); } /** * Gets all the dependencies of the given roots. The inputs must be returned * in a stable order. In other words, if A comes before B, and A does not * transitively depend on B, then A must also come before B in the returned * list. * * @param sorted If true, get them in topologically sorted order. If false, * get them in the original order they were passed to the compiler. */ public List<INPUT> getDependenciesOf(List<INPUT> roots, boolean sorted) { Preconditions.checkArgument(inputs.containsAll(roots)); Set<INPUT> included = Sets.newHashSet(); Deque<INPUT> worklist = new ArrayDeque<INPUT>(roots); while (!worklist.isEmpty()) { INPUT current = worklist.pop(); if (included.add(current)) { for (String req : current.getRequires()) { INPUT dep = provideMap.get(req); if (dep != null) { worklist.add(dep); } } } } ImmutableList.Builder<INPUT> builder = ImmutableList.builder(); for (INPUT current : (sorted ? sortedList : inputs)) { if (included.contains(current)) { builder.add(current); } } return builder.build(); } public List<INPUT> getInputsWithoutProvides() { return Collections.<INPUT>unmodifiableList(noProvides); } private static <T> List<T> topologicalStableSort( List<T> items, Multimap<T, T> deps) { if (items

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> if (reference.isHoistedFunction()) { blocksWithDeclarations.add(reference.getBasicBlock()); isDeclaredInScope = true; hoistedFn = reference; break; } else if (NodeUtil.isFunctionDeclaration( reference.getNode().getParent())) { isUnhoistedNamedFunction = true; } } for (Reference reference : references) { if (reference == hoistedFn) { continue; } BasicBlock basicBlock = reference.getBasicBlock(); boolean isDeclaration = reference.isDeclaration(); boolean allowDupe = SyntacticScopeCreator.hasDuplicateDeclarationSuppression( reference.getNode(), v); if (isDeclaration && !allowDupe) { // Look through all the declarations we've found so far, and // check if any of them are before this block. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (declaredBlock.provablyExecutesBefore(basicBlock)) { // TODO(johnlenz): Fix AST generating clients that so they would // have property StaticSourceFile attached at each node. Or // better yet, make sure the generated code never violates // the requirement to pass aggressive var check! String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, REDECLARED_VARIABLE, v.name)); break; } } } if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) { // Only allow an unhoisted named function to be used within the // block it is declared. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (!declaredBlock.provablyExecutesBefore(basicBlock)) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), AMBIGUOUS_FUNCTION_DECL, v.name)); break; } } } if (!isDeclaration && !isDeclaredInScope) { // Don't check the order of refer in externs files. if (!reference.getNode().isFromExterns()) { // Special case to deal with var goog = goog || {} Node grandparent = reference.getGrandparent();

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.valueOf(self.nextUniqueNameId()); } }; } @Override boolean areNodesEqualForInlining(Node n1, Node n2) { if (options.ambiguateProperties || options.disambiguateProperties) { // The type based optimizations require that type information is preserved // during other optimizations. return n1.isEquivalentToTyped(n2); } else { return n1.isEquivalentTo(n2); } } //------------------------------------------------------------------------ // Inputs //------------------------------------------------------------------------ // TODO(nicksantos): Decide which parts of these belong in an AbstractCompiler // interface, and which ones should always be injected. @Override public CompilerInput getInput(InputId id) { return inputsById.get(id); } /** * Removes an input file from AST. * @param id The id of the input to be removed. */ protected void removeExternInput(InputId id) { CompilerInput input = getInput(id); if (input == null) { return; } Preconditions.checkState(input.isExtern(), "Not an extern input: %s", input.getName()); inputsById.remove(id); externs.remove(input); Node root = input.getAstRoot(this); if (root != null) { root.detachFromParent(); } } @Override public CompilerInput newExternInput(String name) { SourceAst ast = new SyntheticAst(name); if (inputsById.containsKey(ast.getInputId())) { throw new IllegalArgumentException("Conflicting externs name: " + name); } CompilerInput input = new CompilerInput(ast, true); putCompilerInput(input.getInputId(), input); externsRoot.addChildToFront(ast.getAstRoot(this)); externs.add(0, input); return input; } private CompilerInput putCompilerInput(InputId id, CompilerInput input) { input.setCompiler(this); return inputsById.put(id, input); } /** Add a source input dynamically. Intended for incremental compilation. */ void addIncrementalSourceAst(JsAst ast) { InputId id = ast.getInputId(); Preconditions.checkState(

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>getInput(id) == null, "Duplicate input %s", id.getIdName()); putCompilerInput(id, new CompilerInput(ast)); } /** * Replace a source input dynamically. Intended for incremental * re-compilation. * * If the new source input doesn't parse, then keep the old input * in the AST and return false. * * @return Whether the new AST was attached successfully. */ boolean replaceIncrementalSourceAst(JsAst ast) { CompilerInput oldInput = getInput(ast.getInputId()); Preconditions.checkNotNull(oldInput, "No input to replace: %s", ast.getInputId().getIdName()); Node newRoot = ast.getAstRoot(this); if (newRoot == null) { return false; } Node oldRoot = oldInput.getAstRoot(this); if (oldRoot != null) { oldRoot.getParent().replaceChild(oldRoot, newRoot); } else { getRoot().getLastChild().addChildToBack(newRoot); } CompilerInput newInput = new CompilerInput(ast); putCompilerInput(ast.getInputId(), newInput); JSModule module = oldInput.getModule(); if (module != null) { module.addAfter(newInput, oldInput); module.remove(oldInput); } // Verify the input id is set properly. Preconditions.checkState( newInput.getInputId().equals(oldInput.getInputId())); InputId inputIdOnAst = newInput.getAstRoot(this).getInputId(); Preconditions.checkState(newInput.getInputId().equals(inputIdOnAst)); inputs.remove(oldInput); return true; } /** * Add a new source input dynamically. Intended for incremental compilation. * <p> * If the new source input doesn't parse, it will not be added, and a false * will be returned. * * @param ast the JS Source to add. * @return true if the source was added successfully, false otherwise. * @throws IllegalStateException if an input for this ast already exists. */ boolean addNewSourceAst(JsAst ast) { CompilerInput oldInput = getInput(ast.getInputId()); if (oldInput != null) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> throw new IllegalStateException( "Input already exists: " + ast.getInputId().getIdName()); } Node newRoot = ast.getAstRoot(this); if (newRoot == null) { return false; } getRoot().getLastChild().addChildToBack(newRoot); CompilerInput newInput = new CompilerInput(ast); // TODO(tylerg): handle this for multiple modules at some point. if (moduleGraph == null && !modules.isEmpty()) { // singleton module modules.get(0).add(newInput); } putCompilerInput(ast.getInputId(), newInput); return true; } @Override JSModuleGraph getModuleGraph() { return moduleGraph; } /** * Gets a module graph. This will always return a module graph, even * in the degenerate case when there's only one module. */ JSModuleGraph getDegenerateModuleGraph() { return moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph; } @Override public JSTypeRegistry getTypeRegistry() { if (typeRegistry == null) { typeRegistry = new JSTypeRegistry(oldErrorReporter, options.looseTypes); } return typeRegistry; } @Override public MemoizedScopeCreator getTypedScopeCreator() { return getPassConfig().getTypedScopeCreator(); } @SuppressWarnings("unchecked") DefaultPassConfig ensureDefaultPassConfig() { PassConfig passes = getPassConfig().getBasePassConfig(); Preconditions.checkState(passes instanceof DefaultPassConfig, "PassConfigs must eventually delegate to the DefaultPassConfig"); return (DefaultPassConfig) passes; } public SymbolTable buildKnownSymbolTable() { SymbolTable symbolTable = new SymbolTable(getTypeRegistry()); MemoizedScopeCreator typedScopeCreator = getTypedScopeCreator(); if (typedScopeCreator != null) { symbolTable.addScopes(typedScopeCreator.getAllMemoizedScopes()); symbolTable.addSymbolsFrom(typedScopeCreator); } else { symbolTable.findScopes(this, externsRoot, jsRoot); } GlobalNamespace globalNamespace = ensureDefaultPassConfig().getGlobalNamespace(); if (globalNamespace != null) { symbolTable.addSymbolsFrom(globalNamespace); } ReferenceCollecting

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>(Node n) { if (phaseOptimizer != null) { phaseOptimizer.reportChangeToEnclosingScope(n); phaseOptimizer.startCrossScopeReporting(); reportCodeChange(); phaseOptimizer.endCrossScopeReporting(); } else { reportCodeChange(); } } /** * Some tests don't want to call the compiler "wholesale," they may not want * to call check and/or optimize. With this method, tests can execute custom * optimization loops. */ @VisibleForTesting void setPhaseOptimizer(PhaseOptimizer po) { this.phaseOptimizer = po; } @Override public void reportCodeChange() { for (CodeChangeHandler handler : codeChangeHandlers) { handler.reportChange(); } } @Override public CodingConvention getCodingConvention() { CodingConvention convention = options.getCodingConvention(); convention = convention != null ? convention : defaultCodingConvention; return convention; } @Override public boolean isIdeMode() { return options.ideMode; } @Override public boolean acceptEcmaScript5() { switch (options.getLanguageIn()) { case ECMASCRIPT5: case ECMASCRIPT5_STRICT: return true; case ECMASCRIPT3: return false; } throw new IllegalStateException("unexpected language mode"); } public LanguageMode languageMode() { return options.getLanguageIn(); } @Override public boolean acceptConstKeyword() { return options.acceptConstKeyword; } @Override Config getParserConfig() { if (parserConfig == null) { Config.LanguageMode mode; switch (options.getLanguageIn()) { case ECMASCRIPT3: mode = Config.LanguageMode.ECMASCRIPT3; break; case ECMASCRIPT5: mode = Config.LanguageMode.ECMASCRIPT5; break; case ECMASCRIPT5_STRICT: mode = Config.LanguageMode.ECMASCRIPT5_STRICT; break; default: throw new IllegalStateException("unexpected language mode"); } parserConfig = ParserRunner.createConfig( isIdeMode(), mode, acceptConstKeyword(), options.extraAnnotationNames); } return parserConfig; }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> runHotSwap(originalRoot, js, this.getCleanupPassConfig()); // NOTE: If hot swap passes that use GlobalNamespace are added, we will need // to revisit this approach to clearing GlobalNamespaces runHotSwapPass(null, null, ensureDefaultPassConfig().garbageCollectChecks); this.getTypeRegistry().clearNamedTypes(); this.removeSyntheticVarsInput(); runHotSwap(originalRoot, js, this.ensureDefaultPassConfig()); } /** * Execute the passes from a PassConfig instance over a single replaced file. */ private void runHotSwap( Node originalRoot, Node js, PassConfig passConfig) { for (PassFactory passFactory : passConfig.getChecks()) { runHotSwapPass(originalRoot, js, passFactory); } } private void runHotSwapPass( Node originalRoot, Node js, PassFactory passFactory) { HotSwapCompilerPass pass = passFactory.getHotSwapPass(this); if (pass != null) { logger.info("Performing HotSwap for pass " + passFactory.getName()); pass.hotSwapScript(js, originalRoot); } } private PassConfig getCleanupPassConfig() { return new CleanupPasses(getOptions()); } private void removeSyntheticVarsInput() { String sourceName = Compiler.SYNTHETIC_EXTERNS; removeExternInput(new InputId(sourceName)); } @Override Node ensureLibraryInjected(String resourceName) { if (injectedLibraries.containsKey(resourceName)) { return null; } // All libraries depend on js/base.js boolean isBase = "base".equals(resourceName); if (!isBase) { ensureLibraryInjected("base"); } Node firstChild = loadLibraryCode(resourceName).removeChildren(); Node lastChild = firstChild.getLastSibling(); Node parent = getNodeForCodeInsertion(null); if (isBase) { parent.addChildrenToFront(firstChild); } else { parent.addChildrenAfter( firstChild, injectedLibraries.get("base")); } reportCodeChange(); injectedLibraries.put(resourceName, lastChild); return lastChild; } /** Load a library as a resource */ @VisibleForTesting Node loadLibraryCode

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Token.NULL: case Token.THIS: case Token.TRUE: validateChildless(n); return; // General unary ops case Token.DELPROP: case Token.POS: case Token.NEG: case Token.NOT: case Token.INC: case Token.DEC: case Token.TYPEOF: case Token.VOID: case Token.BITNOT: case Token.CAST: validateUnaryOp(n); return; // General binary ops case Token.COMMA: case Token.OR: case Token.AND: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.LT: case Token.GT: case Token.LE: case Token.GE: case Token.INSTANCEOF: case Token.IN: case Token.LSH: case Token.RSH: case Token.URSH: case Token.SUB: case Token.ADD: case Token.MUL: case Token.MOD: case Token.DIV: validateBinaryOp(n); return; // Assignments case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: validateAssignmentExpression(n); return; case Token.HOOK: validateTrinaryOp(n); return; // Node types that require special handling case Token.STRING: validateString(n); return; case Token.NUMBER: validateNumber(n); return; case Token.NAME: validateName(n); return; case Token.GETELEM: validateBinaryOp(n); return; case Token.GETPROP: validateGetProp(n); return; case Token.ARRAYLIT: validateArrayLit(n); return; case

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Compiler compiler, RedeclarationHandler redeclarationHandler) { this.compiler = compiler; this.redeclarationHandler = redeclarationHandler; } @Override public Scope createScope(Node n, Scope parent) { inputId = null; if (parent == null) { scope = Scope.createGlobalScope(n); } else { scope = new Scope(parent, n); } scanRoot(n); inputId = null; Scope returnedScope = scope; scope = null; return returnedScope; } private void scanRoot(Node n) { if (n.isFunction()) { if (inputId == null) { inputId = NodeUtil.getInputId(n); // TODO(johnlenz): inputId maybe null if the FUNCTION node is detached // from the AST. // Is it meaningful to build a scope for detached FUNCTION node? } final Node fnNameNode = n.getFirstChild(); final Node args = fnNameNode.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.isParamList()); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.isName()); declareVar(a); } // Body scanVars(body); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); declareVar(child); child = next; } return; case Token.FUNCTION: if (

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(n.getFirstChild()); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().isName()); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var); scanVars(block); return; // only one child to scan case Token.SCRIPT: inputId = n.getInputId(); Preconditions.checkNotNull(inputId); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, CompilerInput input); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { @Override public void onRedeclaration( Scope s, String name, Node n, CompilerInput input) { Node parent = n.getParent(); // Don't allow multiple variables to be declared at the top-level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name); Node origParent = origVar.getParentNode(); if (origParent.isCatch() && parent.isCatch()) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar); if (!allowDupe) { compiler.report( JSError.make

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>(NodeUtil.getSourceName(n), n, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(NodeUtil.getSourceName(n), n, VAR_ARGUMENTS_SHADOWED_ERROR)); } } } /** * Declares a variable. * * @param n The node corresponding to the variable name. */ private void declareVar(Node n) { Preconditions.checkState(n.isName()); CompilerInput input = compiler.getInput(inputId); String name = n.getString(); if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, input); } else { scope.declare(name, n, null, input); } } /** * @param n The name node to check. * @param origVar The associated Var. * @return Whether duplicated declarations warnings should be suppressed * for the given node. */ static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); Node origParent = origVar.getParentNode(); JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } if (info != null && info.getSuppressions().contains("duplicate")) { return true; } info = origVar.nameNode.getJSDocInfo(); if (info == null) { info = origParent.getJSDocInfo(); } return (info != null && info.getSuppressions().contains("duplicate")); } /** * Generates an untyped global scope from the root of AST of compiler (which * includes externs). * *

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2011 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; /** * {@link CheckDebuggerStatement} checks for the presence of the "debugger" * statement in JavaScript code. It is appropriate to use this statement while * developing JavaScript; however, it is generally undesirable to include it in * production code. * * @author bolinfest@google.com (Michael Bolin) */ class CheckDebuggerStatement extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType DEBUGGER_STATEMENT_PRESENT = DiagnosticType.disabled("JSC_DEBUGGER_STATEMENT_PRESENT", "Using the debugger statement can halt your application if the user " + "has a JavaScript debugger running."); private final AbstractCompiler compiler; public CheckDebuggerStatement(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isDebugger()) { t.report(n, DEBUGGER_STATEMENT_PRESENT); } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> final DiagnosticType ARGUMENTS_DECLARATION = DiagnosticType.warning( "JSC_ARGUMENTS_DECLARATION", "\"arguments\" cannot be redeclared in ES5 strict mode"); static final DiagnosticType ARGUMENTS_ASSIGNMENT = DiagnosticType.warning( "JSC_ARGUMENTS_ASSIGNMENT", "the \"arguments\" object cannot be reassigned in ES5 strict mode"); static final DiagnosticType DELETE_VARIABLE = DiagnosticType.warning( "JSC_DELETE_VARIABLE", "variables, functions, and arguments cannot be deleted in " + "ES5 strict mode"); static final DiagnosticType ILLEGAL_NAME = DiagnosticType.error( "JSC_ILLEGAL_NAME", "identifiers ending in '__' cannot be used in Caja"); static final DiagnosticType DUPLICATE_OBJECT_KEY = DiagnosticType.warning( "JSC_DUPLICATE_OBJECT_KEY", "object literals cannot contain duplicate keys in ES5 strict mode"); static final DiagnosticType BAD_FUNCTION_DECLARATION = DiagnosticType.error( "JSC_BAD_FUNCTION_DECLARATION", "functions can only be declared at top level or immediately within " + "another function in ES5 strict mode"); private final AbstractCompiler compiler; private final boolean noVarCheck; private final boolean noCajaChecks; StrictModeCheck(AbstractCompiler compiler) { this(compiler, false, false); } StrictModeCheck( AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) { this.compiler = compiler; this.noVarCheck = noVarCheck; this.noCajaChecks = noCajaChecks; } @Override public void process(Node externs, Node root) { NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); NodeTraversal.traverse(compiler, root, new NonExternChecks()); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { checkFunctionUse(t, n); } else if (n.isName()) { if (!isDeclaration(n)) { checkNameUse(t, n); } } else if (n.isAssign()) { checkAssignment(t

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>, n); } else if (n.isDelProp()) { checkDelete(t, n); } else if (n.isObjectLit()) { checkObjectLiteral(t, n); } else if (n.isLabel()) { checkLabel(t, n); } } /** Checks that the function is used legally. */ private void checkFunctionUse(NodeTraversal t, Node n) { if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) { t.report(n, BAD_FUNCTION_DECLARATION); } } /** * Determines if the given name is a declaration, which can be a declaration * of a variable, function, or argument. */ private static boolean isDeclaration(Node n) { switch (n.getParent().getType()) { case Token.VAR: case Token.FUNCTION: case Token.CATCH: return true; case Token.PARAM_LIST: return n.getParent().getParent().isFunction(); default: return false; } } /** Checks that the given name is used legally. */ private void checkNameUse(NodeTraversal t, Node n) { Var v = t.getScope().getVar(n.getString()); if (v == null) { // In particular, this prevents creating a global variable by assigning // to it without a declaration. if (!noVarCheck) { t.report(n, UNKNOWN_VARIABLE, n.getString()); } } if (!noCajaChecks) { if ("eval".equals(n.getString())) { t.report(n, EVAL_USE); } else if (n.getString().endsWith("__")) { t.report(n, ILLEGAL_NAME); } } } /** Checks that an assignment is not to the "arguments" object. */ private void checkAssignment(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { if ("arguments".equals(n.getFirstChild().getString())) { t.report(n, ARGUMENTS_ASSIGNMENT); } else if ("eval".equals(n.getFirstChild().getString())) { // Note that assignment to eval is already illegal because any use of // that

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> name is illegal. if (noCajaChecks) { t.report(n, EVAL_ASSIGNMENT); } } } } /** Checks that variables, functions, and arguments are not deleted. */ private void checkDelete(NodeTraversal t, Node n) { if (n.getFirstChild().isName()) { Var v = t.getScope().getVar(n.getFirstChild().getString()); if (v != null) { t.report(n, DELETE_VARIABLE); } } } /** Checks that object literal keys are valid. */ private void checkObjectLiteral(NodeTraversal t, Node n) { Set<String> getters = Sets.newHashSet(); Set<String> setters = Sets.newHashSet(); for (Node key = n.getFirstChild(); key != null; key = key.getNext()) { if (!noCajaChecks && key.getString().endsWith("__")) { t.report(key, ILLEGAL_NAME); } if (!key.isSetterDef()) { // normal property and getter cases if (getters.contains(key.getString())) { t.report(key, DUPLICATE_OBJECT_KEY); } else { getters.add(key.getString()); } } if (!key.isGetterDef()) { // normal property and setter cases if (setters.contains(key.getString())) { t.report(key, DUPLICATE_OBJECT_KEY); } else { setters.add(key.getString()); } } } } /** Checks that label names are valid. */ private void checkLabel(NodeTraversal t, Node n) { if (n.getFirstChild().getString().endsWith("__")) { if (!noCajaChecks) { t.report(n.getFirstChild(), ILLEGAL_NAME); } } } /** Checks that are performed on non-extern code only. */ private class NonExternChecks extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if ((n.isName()) && isDeclaration(n)) { checkDeclaration(t, n); } else if (n.isGetProp()) { checkProperty(t

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>can.c. */ public final static int ERROR = -1, RETURN = 4, BITOR = 9, BITXOR = 10, BITAND = 11, EQ = 12, NE = 13, LT = 14, LE = 15, GT = 16, GE = 17, LSH = 18, RSH = 19, URSH = 20, ADD = 21, SUB = 22, MUL = 23, DIV = 24, MOD = 25, NOT = 26, BITNOT = 27, POS = 28, NEG = 29, NEW = 30, DELPROP = 31, TYPEOF = 32, GETPROP = 33, GETELEM = 35, CALL = 37, NAME = 38, NUMBER = 39, STRING = 40, NULL = 41, THIS = 42, FALSE = 43, TRUE = 44, SHEQ = 45, // shallow equality (===) SHNE = 46, // shallow inequality (!==) REGEXP = 47, THROW = 49, IN = 51, INSTANCEOF = 52, ARRAYLIT = 63, // array literal OBJECTLIT = 64, // object literal TRY = 77, PARAM_LIST = 83, COMMA = 85, // comma operator ASSIGN = 86, // simple assignment (=) ASSIGN_BITOR = 87, // |= ASSIGN_BITXOR = 88, // ^= ASSIGN_BITAND = 89, // &= ASSIGN_LSH = 90, // <<=

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> ASSIGN_RSH = 91, // >>= ASSIGN_URSH = 92, // >>>= ASSIGN_ADD = 93, // += ASSIGN_SUB = 94, // -= ASSIGN_MUL = 95, // *= ASSIGN_DIV = 96, // /= ASSIGN_MOD = 97, // %= HOOK = 98, // conditional (?:) OR = 100, // logical or (||) AND = 101, // logical and (&&) INC = 102, // increment (++) DEC = 103, // decrement (--) FUNCTION = 105, // function keyword IF = 108, // if keyword SWITCH = 110, // switch keyword CASE = 111, // case keyword DEFAULT_CASE = 112, // default keyword WHILE = 113, // while keyword DO = 114, // do keyword FOR = 115, // for keyword BREAK = 116, // break keyword CONTINUE = 117, // continue keyword VAR = 118, // var keyword WITH = 119, // with keyword CATCH = 120, // catch keyword VOID = 122, // void keyword EMPTY = 124, BLOCK = 125, // statement block LABEL = 126, // label EXPR_RESULT = 130, // expression statement in scripts SCRIPT = 132, // top-level node for entire script GETTER_DEF = 147, SETTER_DEF = 148, CONST = 149, // JS 1.5 const keyword DEBUGGER = 152, // JSCompiler introduced tokens LABEL_NAME = 153, STRING_KEY = 154, // object literal key CAST = 155, // J

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>SDoc-only tokens ANNOTATION = 300, PIPE = 301, STAR = 302, EOC = 303, QMARK = 304, ELLIPSIS = 305, BANG = 306, EQUALS = 307, LB = 308, // left brackets LC = 309, // left curly braces COLON = 310; // Transitional definitions // TODO(johnlenz): remove these public final static int DEFAULT = DEFAULT_CASE, GET = GETTER_DEF, LP = PARAM_LIST, SET = SETTER_DEF; public static String name(int token) { switch (token) { case ERROR: return "ERROR"; case RETURN: return "RETURN"; case BITOR: return "BITOR"; case BITXOR: return "BITXOR"; case BITAND: return "BITAND"; case EQ: return "EQ"; case NE: return "NE"; case LT: return "LT"; case LE: return "LE"; case GT: return "GT"; case GE: return "GE"; case LSH: return "LSH"; case RSH: return "RSH"; case URSH: return "URSH"; case ADD: return "ADD"; case SUB: return "SUB"; case MUL: return "MUL"; case DIV: return "DIV"; case MOD: return "MOD"; case NOT: return "NOT"; case BITNOT: return "BITNOT"; case POS: return "POS"; case NEG: return "NEG"; case NEW: return "NEW"; case DELPROP: return "DELPROP"; case TYPEOF: return "TYPEOF"; case GETPROP: return "GETPROP"; case GETELEM: return "GETELEM"; case CALL: return "CALL"; case NAME: return "NAME"; case LABEL_NAME: return "LABEL_NAME"; case NUMBER: return

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> "NUMBER"; case STRING: return "STRING"; case STRING_KEY: return "STRING_KEY"; case NULL: return "NULL"; case THIS: return "THIS"; case FALSE: return "FALSE"; case TRUE: return "TRUE"; case SHEQ: return "SHEQ"; case SHNE: return "SHNE"; case REGEXP: return "REGEXP"; case THROW: return "THROW"; case IN: return "IN"; case INSTANCEOF: return "INSTANCEOF"; case ARRAYLIT: return "ARRAYLIT"; case OBJECTLIT: return "OBJECTLIT"; case TRY: return "TRY"; case PARAM_LIST: return "PARAM_LIST"; case COMMA: return "COMMA"; case ASSIGN: return "ASSIGN"; case ASSIGN_BITOR: return "ASSIGN_BITOR"; case ASSIGN_BITXOR: return "ASSIGN_BITXOR"; case ASSIGN_BITAND: return "ASSIGN_BITAND"; case ASSIGN_LSH: return "ASSIGN_LSH"; case ASSIGN_RSH: return "ASSIGN_RSH"; case ASSIGN_URSH: return "ASSIGN_URSH"; case ASSIGN_ADD: return "ASSIGN_ADD"; case ASSIGN_SUB: return "ASSIGN_SUB"; case ASSIGN_MUL: return "ASSIGN_MUL"; case ASSIGN_DIV: return "ASSIGN_DIV"; case ASSIGN_MOD: return "ASSIGN_MOD"; case HOOK: return "HOOK"; case OR: return "OR"; case AND: return "AND"; case INC: return "INC"; case DEC: return "DEC"; case FUNCTION: return "FUNCTION"; case IF: return "IF"; case SWITCH: return "SWITCH"; case CASE: return "CASE"; case DEFAULT_CASE: return "DEFAULT_CASE"; case WHILE: return "WHILE"; case DO: return "DO"; case FOR: return "FOR"; case BREAK: return "BREAK"; case CONTINUE: return "CONTINUE"; case VAR: return "VAR";

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>) ? CheckLevel.ERROR : CheckLevel.WARNING; // TODO(anatol): add flag that decides whether to process UNNAMED messages. // Some projects would not want such functionality (unnamed) as they don't // use SOY templates. } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) { compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(), checkLevel, MESSAGE_NODE_IS_ORPHANED)); } } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { String messageKey; boolean isVar; Node msgNode, msgNodeParent; switch (node.getType()) { case Token.NAME: // var MSG_HELLO = 'Message' if ((parent != null) && (parent.isVar())) { messageKey = node.getString(); isVar = true; } else { return; } msgNode = node.getFirstChild(); msgNodeParent = node; break; case Token.ASSIGN: // somenamespace.someclass.MSG_HELLO = 'Message' isVar = false; Node getProp = node.getFirstChild(); if (!getProp.isGetProp()) { return; } Node propNode = getProp.getLastChild(); messageKey = propNode.getString(); msgNode = node.getLastChild(); msgNodeParent = node; break; case Token.CALL: // goog.getMsg() String fnName = node.getFirstChild().getQualifiedName(); if (MSG_FUNCTION_NAME.equals(fnName)) { googMsgNodes.put(node, traversal.getSourceName()); } else if (MSG_FALLBACK_FUNCTION_NAME.equals(fnName)) { visitFallbackFunctionCall(traversal, node); } return; default: return; } // Is this a message name? boolean isNewStyleMessage = msgNode != null && msgNode.isCall(); if (!isMessageName(messageKey, isNewStyleMessage)) { return; } if (

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>msgNode == null) { compiler.report( traversal.makeError(node, MESSAGE_HAS_NO_VALUE, messageKey)); return; } // Just report a warning if a qualified messageKey that looks like a message // (e.g. "a.b.MSG_X") doesn't use goog.getMsg(). if (isNewStyleMessage) { googMsgNodes.remove(msgNode); } else if (style != JsMessage.Style.LEGACY) { compiler.report(traversal.makeError(node, checkLevel, MESSAGE_NOT_INITIALIZED_USING_NEW_SYNTAX)); } boolean isUnnamedMsg = isUnnamedMessageName(messageKey); Builder builder = new Builder( isUnnamedMsg ? null : messageKey); builder.setSourceName(traversal.getSourceName()); try { if (isVar) { extractMessageFromVariable(builder, node, parent, parent.getParent()); } else { extractMessageFromProperty(builder, node.getFirstChild(), node); } } catch (MalformedException ex) { compiler.report(traversal.makeError(ex.getNode(), MESSAGE_TREE_MALFORMED, ex.getMessage())); return; } JsMessage extractedMessage = builder.build(idGenerator); // If asked to check named internal messages. if (needToCheckDuplications && !isUnnamedMsg && !extractedMessage.isExternal()) { checkIfMessageDuplicated(messageKey, msgNode); } trackMessage(traversal, extractedMessage, messageKey, msgNode, isUnnamedMsg); if (extractedMessage.isEmpty()) { // value of the message is an empty string. Translators do not like it. compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_TEXT, messageKey)); } // New-style messages must have descriptions. We don't emit a warning // for legacy-style messages, because there are thousands of // them in legacy code that are not worth the effort to fix, since they've // already been translated anyway. String desc = extractedMessage.getDesc(); if (isNewStyleMessage && (desc == null || desc.trim().isEmpty()) && !extractedMessage.isExternal()) { compiler.report(

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>); if (objType != null && objType.getImplicitPrototype() != null) { typeSystem.addInvalidatingType(objType.getImplicitPrototype()); recordInvalidationError(objType.getImplicitPrototype(), error); } } } /** Returns the property for the given name, creating it if necessary. */ protected Property getProperty(String name) { if (!properties.containsKey(name)) { properties.put(name, new Property(name)); } return properties.get(name); } /** Public for testing. */ T getTypeWithProperty(String field, T type) { return typeSystem.getTypeWithProperty(field, type); } /** Tracks the current type system scope while traversing. */ private abstract class AbstractScopingCallback implements ScopedCallback { protected final Stack<StaticScope<T>> scopes = new Stack<StaticScope<T>>(); @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { scopes.push(typeSystem.getRootScope()); } else { scopes.push(typeSystem.getFunctionScope(t.getScopeRoot())); } } @Override public void exitScope(NodeTraversal t) { scopes.pop(); } /** Returns the current scope at this point in the file. */ protected StaticScope<T> getScope() { return scopes.peek(); } } /** * Finds all properties defined in the externs file and sets them as * ineligible for renaming from the type on which they are defined. */ private class FindExternProperties extends AbstractScopingCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { // TODO(johnlenz): Support object-literal property definitions. if (n.isGetProp()) { String field = n.getLastChild().getString(); T type = typeSystem.getType(getScope(), n.getFirstChild(), field); Property prop = getProperty(field); if (typeSystem.isInvalidatingType(type)) { prop.invalidate(); } else { prop.addTypeToSkip(type); //

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> If this is a prototype property, then we want to skip assignments // to the instance type as well. These assignments are not usually // seen in the extern code itself, so we must handle them here. if ((type = typeSystem.getInstanceFromPrototype(type)) != null) { prop.getTypes().add(type); prop.typesToSkip.add(type); } } } } } /** * Traverses the tree, building a map from field names to Nodes for all * fields that can be renamed. */ private class FindRenameableProperties extends AbstractScopingCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isGetProp()) { handleGetProp(t, n); } else if (n.isObjectLit()) { handleObjectLit(t, n); } } /** * Processes a GETPROP node. */ private void handleGetProp(NodeTraversal t, Node n) { String name = n.getLastChild().getString(); T type = typeSystem.getType(getScope(), n.getFirstChild(), name); Property prop = getProperty(name); if (!prop.scheduleRenaming(n.getLastChild(), processProperty(t, prop, type, null))) { if (propertiesToErrorFor.containsKey(name)) { String suggestion = ""; if (type instanceof JSType) { JSType jsType = (JSType) type; if (jsType.isAllType() || jsType.isUnknownType()) { if (n.getFirstChild().isThis()) { suggestion = "The \"this\" object is unknown in the function," + "consider using @this"; } else { String qName = n.getFirstChild().getQualifiedName(); suggestion = "Consider casting " + qName + " if you know it's type."; } } else { List<String> errors = Lists.newArrayList(); printErrorLocations(errors, jsType); if (!errors.isEmpty()) { suggestion = "Consider fixing errors for the following types:\n"; suggestion += Joiner.on("\n").join(errors); } } } compiler.report(JSError.make( t.getSourceName(), n,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> members of the union. */ private T processProperty( NodeTraversal t, Property prop, T type, T relatedType) { type = typeSystem.restrictByNotNullOrUndefined(type); if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) { return null; } Iterable<T> alternatives = typeSystem.getTypeAlternatives(type); if (alternatives != null) { T firstType = relatedType; for (T subType : alternatives) { T lastType = processProperty(t, prop, subType, firstType); if (lastType != null) { firstType = firstType == null ? lastType : firstType; } } return firstType; } else { T topType = typeSystem.getTypeWithProperty(prop.name, type); if (typeSystem.isInvalidatingType(topType)) { return null; } prop.addType(type, topType, relatedType); return topType; } } } /** Renames all properties with references on more than one type. */ void renameProperties() { int propsRenamed = 0, propsSkipped = 0, instancesRenamed = 0, instancesSkipped = 0, singleTypeProps = 0; Set<String> reported = Sets.newHashSet(); for (Property prop : properties.values()) { if (prop.shouldRename()) { Map<T, String> propNames = buildPropNames(prop.getTypes(), prop.name); ++propsRenamed; prop.expandTypesToSkip(); for (Node node : prop.renameNodes) { T rootType = prop.rootTypes.get(node); if (prop.shouldRename(rootType)) { String newName = propNames.get(rootType); node.setString(newName); compiler.reportCodeChange(); ++instancesRenamed; } else { ++instancesSkipped; CheckLevel checkLevelForProp = propertiesToErrorFor.get(prop.name); if (checkLevelForProp != null && checkLevelForProp != CheckLevel.OFF && !reported.contains(prop.name)) { reported.add(prop.name); compiler.report(JSError.make( Node

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSTypeNative; import com.google.javascript.rhino.jstype.JSTypeRegistry; import com.google.javascript.rhino.jstype.ObjectType; import java.util.Set; /** * A code generator that outputs type annotations for functions and * constructors. */ class TypedCodeGenerator extends CodeGenerator { private final JSTypeRegistry registry; TypedCodeGenerator( CodeConsumer consumer, CompilerOptions options, JSTypeRegistry registry) { super(consumer, options); Preconditions.checkNotNull(registry); this.registry = registry; } @Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent != null && (parent.isBlock() || parent.isScript())) { if (n.isFunction()) { add(getFunctionAnnotation(n)); } else if (n.isExprResult() && n.getFirstChild().isAssign()) { Node rhs = n.getFirstChild().getLastChild(); add(getTypeAnnotation(rhs)); } else if (n.is

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> {2} property."); static final DiagnosticType CONST_PROPERTY_REASSIGNED_VALUE = DiagnosticType.warning( "JSC_CONSTANT_PROPERTY_REASSIGNED_VALUE", "constant property {0} assigned a value more than once"); static final DiagnosticType CONST_PROPERTY_DELETED = DiagnosticType.warning( "JSC_CONSTANT_PROPERTY_DELETED", "constant property {0} cannot be deleted"); private final AbstractCompiler compiler; private final TypeValidator validator; // State about the current traversal. private int deprecatedDepth = 0; private int methodDepth = 0; private JSType currentClass = null; private final Multimap<String, String> initializedConstantProperties; CheckAccessControls(AbstractCompiler compiler) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.initializedConstantProperties = HashMultimap.create(); } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverse(compiler, scriptRoot, this); } @Override public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n)) { deprecatedDepth++; } if (methodDepth == 0) { currentClass = getClassOfMethod(n, parent); } methodDepth++; } } @Override public void exitScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); if (isDeprecatedFunction(n)) { deprecatedDepth--; } methodDepth--; if (methodDepth == 0) { currentClass = null; } } } /** * Gets the type of the class that "owns" a method, or null if * we know that its un-owned. */ private JSType getClassOfMethod(Node n, Node parent) { if (parent.isAssign()) { Node lValue = parent.getFirstChild(); if (NodeUtil.isGet(lValue)) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> // We have an assignment of the form "a.b = ...". JSType lValueType = lValue.getJSType(); if (lValueType != null && lValueType.isNominalConstructor()) { // If a.b is a constructor, then everything in this function // belongs to the "a.b" type. return (lValueType.toMaybeFunctionType()).getInstanceType(); } else { // If a.b is not a constructor, then treat this as a method // of whatever type is on "a". return normalizeClassType(lValue.getFirstChild().getJSType()); } } else { // We have an assignment of the form "a = ...", so pull the // type off the "a". return normalizeClassType(lValue.getJSType()); } } else if (NodeUtil.isFunctionDeclaration(n) || parent.isName()) { return normalizeClassType(n.getJSType()); } return null; } /** * Normalize the type of a constructor, its instance, and its prototype * all down to the same type (the instance type). */ private JSType normalizeClassType(JSType type) { if (type == null || type.isUnknownType()) { return type; } else if (type.isNominalConstructor()) { return (type.toMaybeFunctionType()).getInstanceType(); } else if (type.isFunctionPrototypeType()) { FunctionType owner = ((ObjectType) type).getOwnerFunction(); if (owner.isConstructor()) { return owner.getInstanceType(); } } return type; } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: checkNameDeprecation(t, n, parent); checkNameVisibility(t, n, parent); break; case Token.GETPROP: checkPropertyDeprecation(t, n, parent); checkPropertyVisibility(t, n, parent); checkConstantProperty(t, n); break; case Token.NEW: checkConstructorDeprecation(t, n, parent); break; case Token.FUNCTION: check

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>FinalClassOverrides(t, n, parent); break; } } /** * Checks the given NEW node to ensure that access restrictions are obeyed. */ private void checkConstructorDeprecation(NodeTraversal t, Node n, Node parent) { JSType type = n.getJSType(); if (type != null) { String deprecationInfo = getTypeDeprecationInfo(type); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_CLASS_REASON, type.toString(), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_CLASS, type.toString())); } } } } /** * Checks the given NAME node to ensure that access restrictions are obeyed. */ private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking definitions or constructors. if (parent.isFunction() || parent.isVar() || parent.isNew()) { return; } Scope.Var var = t.getScope().getVar(n.getString()); JSDocInfo docInfo = var == null ? null : var.getJSDocInfo(); if (docInfo != null && docInfo.isDeprecated() && shouldEmitDeprecationWarning(t, n, parent)) { if (docInfo.getDeprecationReason() != null) { compiler.report( t.makeError(n, DEPRECATED_NAME_REASON, n.getString(), docInfo.getDeprecationReason())); } else { compiler.report( t.makeError(n, DEPRECATED_NAME, n.getString())); } } } /** * Checks the given GETPROP node to ensure that access restrictions are * obeyed. */ private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking constructors. if (parent.isNew()) { return; } ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); String propertyName = n.getLastChild().getString(); if (objectType != null

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>) { String deprecationInfo = getPropertyDeprecationInfo(objectType, propertyName); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_PROP_REASON, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_PROP, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(NodeTraversal t, Node name, Node parent) { Var var = t.getScope().getVar(name.getString()); if (var != null) { JSDocInfo docInfo = var.getJSDocInfo(); if (docInfo != null) { // If a name is private, make sure that we're in the same file. Visibility visibility = docInfo.getVisibility(); if (visibility == Visibility.PRIVATE) { StaticSourceFile varSrc = var.getSourceFile(); StaticSourceFile refSrc = name.getStaticSourceFile(); if (varSrc != null && refSrc != null && !varSrc.getName().equals(refSrc.getName())) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } compiler.report( t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS, name.getString(), varSrc.getName())); } } } } } /** * Checks if a constructor is trying to override a final class. */ private void checkFinalClassOverrides(NodeTraversal t, Node fn, Node parent) { JSType type = fn.getJSType().toMaybeFunctionType(); if (type != null && type.isConstructor()) { JSType finalParentClass = getFinalParentClass(getClassOfMethod(fn, parent)); if (finalParentClass != null) { compiler.report( t.makeError(fn, EXTEND_FINAL_CLASS,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> type.getDisplayName(), finalParentClass.getDisplayName())); } } } /** * Determines whether the given property with @const tag got reassigned * @param t The current traversal. * @param getprop The getprop node. */ private void checkConstantProperty(NodeTraversal t, Node getprop) { // Check whether the property is modified Node parent = getprop.getParent(); boolean isDelete = parent.isDelProp(); if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop) && !parent.isInc() && !parent.isDec() && !isDelete) { return; } ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName); // Check whether constant properties are reassigned if (isConstant) { if (isDelete) { compiler.report( t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName)); return; } ObjectType oType = objectType; while (oType != null) { if (oType.hasReferenceName()) { if (initializedConstantProperties.containsEntry( oType.getReferenceName(), propertyName)) { compiler.report( t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE, propertyName)); break; } } oType = oType.getImplicitPrototype(); } Preconditions.checkState(objectType.hasReferenceName()); initializedConstantProperties.put(objectType.getReferenceName(), propertyName); // Add the prototype when we're looking at an instance object if (objectType.isInstanceType()) { ObjectType prototype = objectType.getImplicitPrototype(); if (prototype != null) { if (prototype.hasProperty(propertyName) && prototype.hasReferenceName()) { initializedConstantProperties.put(prototype.getReferenceName(), propertyName); } } } } } /** * Determines whether the given property is visible in the current context. * @param t The current traversal. * @param getprop The getprop node. */ private void checkPropertyVisibility(NodeTraversal

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> t, Node getprop, Node parent) { ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); if (objectType != null) { // Is this a normal property access, or are we trying to override // an existing property? boolean isOverride = parent.getJSDocInfo() != null && parent.isAssign() && parent.getFirstChild() == getprop; // Find the lowest property defined on a class with visibility // information. if (isOverride) { objectType = objectType.getImplicitPrototype(); } JSDocInfo docInfo = null; for (; objectType != null; objectType = objectType.getImplicitPrototype()) { docInfo = objectType.getOwnPropertyJSDocInfo(propertyName); if (docInfo != null && docInfo.getVisibility() != Visibility.INHERITED) { break; } } if (objectType == null) { // We couldn't find a visibility modifier; assume it's public. return; } String referenceSource = getprop.getSourceFileName(); String definingSource = docInfo.getSourceName(); boolean sameInput = referenceSource != null && referenceSource.equals(definingSource); Visibility visibility = docInfo.getVisibility(); JSType ownerType = normalizeClassType(objectType); if (isOverride) { // Check an ASSIGN statement that's trying to override a property // on a superclass. JSDocInfo overridingInfo = parent.getJSDocInfo(); Visibility overridingVisibility = overridingInfo == null ? Visibility.INHERITED : overridingInfo.getVisibility(); // Check that (a) the property *can* be overridden, and // (b) that the visibility of the override is the same as the // visibility of the original property. if (visibility == Visibility.PRIVATE && !sameInput) { compiler.report( t.makeError(getprop, PRIVATE_OVERRIDE, objectType.toString())); } else if (overridingVisibility != Visibility.INHERITED && overridingVisibility != visibility) { compiler.report( t.makeError(getprop, VISIBILITY_MISMATCH, visibility.name(), objectType

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.toString(), overridingVisibility.name())); } } else { if (sameInput) { // private access is always allowed in the same file. return; } else if (visibility == Visibility.PRIVATE && (currentClass == null || !ownerType.isEquivalentTo(currentClass))) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } // private access is not allowed outside the file from a different // enclosing class. compiler.report( t.makeError(getprop, BAD_PRIVATE_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } else if (visibility == Visibility.PROTECTED) { // There are 3 types of legal accesses of a protected property: // 1) Accesses in the same file // 2) Overriding the property in a subclass // 3) Accessing the property from inside a subclass // The first two have already been checked for. if (currentClass == null || !currentClass.isSubtype(ownerType)) { compiler.report( t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } } } } } /** * Whether the given access of a private constructor is legal. * * For example, * new PrivateCtor_(); // not legal * PrivateCtor_.newInstance(); // legal * x instanceof PrivateCtor_ // legal * * This is a weird special case, because our visibility system is inherited * from Java, and JavaScript has no distinction between classes and * constructors like Java does. * * We may want to revisit this if we decide to make the restrictions tighter. */ private static boolean isValidPrivateConstructorAccess(Node parent) { return !parent.isNew(); } /** * Determines whether a deprecation warning should be emitted. * @param t The current traversal. * @param n The node which we are checking. * @param parent The parent of the node which we are checking. */ private boolean shouldEmitDeprecationWarning( NodeTraversal t, Node n, Node parent) { // In

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> the global scope, there are only two kinds of accesses that should // be flagged for warnings: // 1) Calls of deprecated functions and methods. // 2) Instantiations of deprecated classes. // For now, we just let everything else by. if (t.inGlobalScope()) { if (!((parent.isCall() && parent.getFirstChild() == n) || n.isNew())) { return false; } } // We can always assign to a deprecated property, to keep it up to date. if (n.isGetProp() && n == parent.getFirstChild() && NodeUtil.isAssignmentOp(parent)) { return false; } return !canAccessDeprecatedTypes(t); } /** * Returns whether it's currently OK to access deprecated names and * properties. * * There are 3 exceptions when we're allowed to use a deprecated * type or property: * 1) When we're in a deprecated function. * 2) When we're in a deprecated class. * 3) When we're in a static method of a deprecated class. */ private boolean canAccessDeprecatedTypes(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); Node scopeRootParent = scopeRoot.getParent(); return // Case #1 (deprecatedDepth > 0) || // Case #2 (getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) || // Case #3 (scopeRootParent != null && scopeRootParent.isAssign() && getTypeDeprecationInfo( getClassOfMethod(scopeRoot, scopeRootParent)) != null); } /** * Returns whether this is a function node annotated as deprecated. */ private static boolean isDeprecatedFunction(Node n) { if (n.isFunction()) { JSType type = n.getJSType(); if (type != null) { return getTypeDeprecationInfo(type) != null; } } return false; } /** * Returns the deprecation reason for the type if it is marked * as being deprecated. Returns empty string if the type is deprecated * but no reason was given. Returns null if the type is not deprecated. */ private static String getTypeDeprecationInfo(JSType type

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Blacklist; Preconditions.checkState(blacklist != null && !blacklist.isEmpty(), "Not checking use of goog.getCssName because of empty blacklist."); return new CheckMissingGetCssName( compiler, options.checkMissingGetCssNameLevel, blacklist); } }; /** * Processes goog.getCssName. The cssRenamingMap is used to lookup * replacement values for the classnames. If null, the raw class names are * inlined. */ final PassFactory closureReplaceGetCssName = new PassFactory("closureReplaceGetCssName", true) { @Override protected CompilerPass create(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { Map<String, Integer> newCssNames = null; if (options.gatherCssNames) { newCssNames = Maps.newHashMap(); } ReplaceCssNames pass = new ReplaceCssNames( compiler, newCssNames, options.cssRenamingWhitelist); pass.process(externs, jsRoot); cssNames = newCssNames; } }; } }; /** * Creates synthetic blocks to prevent FoldConstants from moving code * past markers in the source. */ final PassFactory createSyntheticBlocks = new PassFactory("createSyntheticBlocks", true) { @Override protected CompilerPass create(AbstractCompiler compiler) { return new CreateSyntheticBlocks(compiler, options.syntheticBlockStartMarker, options.syntheticBlockEndMarker); } }; /** Various peephole optimizations. */ final PassFactory peepholeOptimizations = new PassFactory("peepholeOptimizations", false) { @Override protected CompilerPass create(AbstractCompiler compiler) { final boolean late = false; return new PeepholeOptimizationsPass(compiler, new PeepholeMinimizeConditions(late), new PeepholeSubstituteAlternateSyntax(late), new PeepholeReplaceKnownMethods(late), new PeepholeRemoveDeadCode(), new PeepholeFoldConstants(late), new PeepholeCollectPropertyAssignments()); } }; /** Same as peepholeOptimizations but aggressively

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> merges code together */ final PassFactory latePeepholeOptimizations = new PassFactory("latePeepholeOptimizations", true) { @Override protected CompilerPass create(AbstractCompiler compiler) { final boolean late = true; return new PeepholeOptimizationsPass(compiler, new StatementFusion(options.aggressiveFusion), new PeepholeRemoveDeadCode(), new PeepholeMinimizeConditions(late), new PeepholeSubstituteAlternateSyntax(late), new PeepholeReplaceKnownMethods(late), new PeepholeFoldConstants(late), new ReorderConstantExpression()); } }; /** Checks that all variables are defined. */ final HotSwapPassFactory checkVars = new HotSwapPassFactory("checkVars", true) { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler) { return new VarCheck(compiler); } }; /** Checks for RegExp references. */ final PassFactory checkRegExp = new PassFactory("checkRegExp", true) { @Override protected CompilerPass create(final AbstractCompiler compiler) { final CheckRegExp pass = new CheckRegExp(compiler); return new CompilerPass() { @Override public void process(Node externs, Node root) { pass.process(externs, root); compiler.setHasRegExpGlobalReferences( pass.isGlobalRegExpPropertiesUsed()); } }; } }; /** Checks that references to variables look reasonable. */ final HotSwapPassFactory checkVariableReferences = new HotSwapPassFactory("checkVariableReferences", true) { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler) { return new VariableReferenceCheck( compiler, options.aggressiveVarCheck); } }; /** Pre-process goog.testing.ObjectPropertyString. */ final PassFactory objectPropertyStringPreprocess = new PassFactory("ObjectPropertyStringPreprocess", true) { @Override protected CompilerPass create(AbstractCompiler compiler) { return new ObjectPropertyStringPreprocess(compiler); } }; /** Creates a typed scope and adds types to the type registry. */ final HotSwapPassFactory resolveTypes = new HotSwapPassFactory("resolveTypes", true) { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> scriptRoot, Node originalRoot) { makeTypeCheck(compiler).check(scriptRoot, false); } }; } }; /** * Checks possible execution paths of the program for problems: missing return * statements and dead code. */ final HotSwapPassFactory checkControlFlow = new HotSwapPassFactory("checkControlFlow", true) { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler) { List<Callback> callbacks = Lists.newArrayList(); if (options.checkUnreachableCode.isOn()) { callbacks.add( new CheckUnreachableCode(compiler, options.checkUnreachableCode)); } if (options.checkMissingReturn.isOn() && options.checkTypes) { callbacks.add( new CheckMissingReturn(compiler, options.checkMissingReturn)); } return combineChecks(compiler, callbacks); } }; /** Checks access controls. Depends on type-inference. */ final HotSwapPassFactory checkAccessControls = new HotSwapPassFactory("checkAccessControls", true) { @Override protected HotSwapCompilerPass create(AbstractCompiler compiler) { return new CheckAccessControls(compiler); } }; /** Executes the given callbacks with a {@link CombinedCompilerPass}. */ private static HotSwapCompilerPass combineChecks(AbstractCompiler compiler, List<Callback> callbacks) { Preconditions.checkArgument(callbacks.size() > 0); Callback[] array = callbacks.toArray(new Callback[callbacks.size()]); return new CombinedCompilerPass(compiler, array); } /** A compiler pass that resolves types in the global scope. */ class GlobalTypeResolver implements HotSwapCompilerPass { private final AbstractCompiler compiler; GlobalTypeResolver(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { if (topScope == null) { regenerateGlobalTypedScope(compiler, root.getParent()); } else { compiler.getTypeRegistry().resolveTypesInScope(topScope); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { patchGlobalTypedScope(compiler, scriptRoot); } } /** A compiler pass that clears the global scope. */ class ClearTypedScope implements

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>. * * @param callNode A CALL node. */ public SubclassRelationship getClassesDefinedByCall(Node callNode); /** * Returns true if passed a string referring to the superclass. The string * will usually be from the string node at the right of a GETPROP, e.g. * this.superClass_. */ public boolean isSuperClassReference(String propertyName); /** * Convenience method for determining provided dependencies amongst different * JS scripts. */ public String extractClassNameIfProvide(Node node, Node parent); /** * Convenience method for determining required dependencies amongst different * JS scripts. */ public String extractClassNameIfRequire(Node node, Node parent); /** * Function name used when exporting properties. * Signature: fn(object, publicName, symbol). * @return function name. */ public String getExportPropertyFunction(); /** * Function name used when exporting symbols. * Signature: fn(publicPath, object). * @return function name. */ public String getExportSymbolFunction(); /** * Checks if the given CALL node is forward-declaring any types, * and returns the name of the types if it is. */ public List<String> identifyTypeDeclarationCall(Node n); /** * In many JS libraries, the function that produces inheritance also * adds properties to the superclass and/or subclass. */ public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type); /** * Function name for abstract methods. An abstract method can be assigned to * an interface method instead of an function expression in order to avoid * linter warnings produced by assigning a function without a return value * where a return value is expected. * @return function name. */ public String getAbstractMethodName(); /** * Checks if the given method defines a singleton getter, and if it does, * returns the name of the class with the singleton getter. By default, always * returns null. Meant to be overridden by subclasses. * * addSingletonGetter needs a coding convention because in the general case, * it can't be inlined. The function inliner sees that it creates an alias * to the given class in an inner closure, and bails out. * *

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>; public Bind(Node target, Node thisValue, Node parameters) { this.target = target; this.thisValue = thisValue; this.parameters = parameters; } /** * The number of parameters bound (not including the 'this' value). */ int getBoundParameterCount() { if (parameters == null) { return 0; } Node paramParent = parameters.getParent(); return paramParent.getChildCount() - paramParent.getIndexOfChild(parameters); } } /** * Whether this CALL function is testing for the existence of a property. */ public boolean isPropertyTestFunction(Node call); /** * Whether this GETPROP node is an alias for an object prototype. */ public boolean isPrototypeAlias(Node getProp); /** * Checks if the given method performs a object literal cast, and if it does, * returns information on the cast. By default, always returns null. Meant * to be overridden by subclasses. * * @param callNode A CALL node. */ public ObjectLiteralCast getObjectLiteralCast(Node callNode); /** * Gets a collection of all properties that are defined indirectly on global * objects. (For example, Closure defines superClass_ in the goog.inherits * call). */ public Collection<String> getIndirectlyDeclaredProperties(); /** * Returns the set of AssertionFunction. */ public Collection<AssertionFunctionSpec> getAssertionFunctions(); /** Specify the kind of inheritance */ static enum SubclassType { INHERITS, MIXIN } /** Record subclass relations */ static class SubclassRelationship { final SubclassType type; final String subclassName; final String superclassName; public SubclassRelationship(SubclassType type, Node subclassNode, Node superclassNode) { this.type = type; this.subclassName = subclassNode.getQualifiedName(); this.superclassName = superclassNode.getQualifiedName(); } } /** * Delegates provides a mechanism and structure for identifying where classes * can call out to optional code to augment their functionality. The optional * code is isolated from the base code through the use of a subclass in the * optional code derived from the delegate class in the base code. */ static class DelegateRelationship { /**

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>, not using another external package would * limit our dependencies. * <p> * TODO(user): All functionality for removing nodes and edges. * * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public abstract class Graph<N, E> implements AdjacencyGraph<N, E> { /** * Pseudo typedef for a pair of annotations. Record of an object's * annotation at some state. */ private static final class AnnotationState { private final Annotatable first; private final Annotation second; public AnnotationState(Annotatable annotatable, Annotation annotation) { this.first = annotatable; this.second = annotation; } } /** * Pseudo typedef for ArrayList<AnnotationState>. Record of a collection of * objects' annotations at some state. */ private static class GraphAnnotationState extends ArrayList<AnnotationState> { private static final long serialVersionUID = 1L; public GraphAnnotationState(int size) { super(size); } } /** * Used by {@link #pushNodeAnnotations()} and {@link #popNodeAnnotations()}. */ private Deque<GraphAnnotationState> nodeAnnotationStack; /** * Used by {@link #pushEdgeAnnotations()} and {@link #popEdgeAnnotations()}. */ private Deque<GraphAnnotationState> edgeAnnotationStack; /** * Connects two nodes in the graph with an edge. * * @param n1 First node. * @param edge The edge. * @param n2 Second node. */ public abstract void connect(N n1, E edge, N n2); /** * Disconnects two nodes in the graph by removing all edges between them. * * @param n1 First node. * @param n2 Second node. */ public abstract void disconnect(N n1, N n2); /** * Connects two nodes in the graph with an edge if such edge does not already * exists between the nodes. * * @param n1 First node. * @param edge The edge. * @param n2 Second node. */ public final void connectIfNotFound(N n1, E edge, N n2) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> : haveAnnotations) { stack.peek().add(new AnnotationState(h, h.getAnnotation())); h.setAnnotation(null); } } /** * Restores the node annotations on the top of stack and pops stack. */ private static void popAnnotations(Deque<GraphAnnotationState> stack) { for (AnnotationState as : stack.pop()) { as.first.setAnnotation(as.second); } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Predicate; import com.google.javascript.jscomp.ControlFlowGraph.Branch; import com.google.javascript.jscomp.NodeTraversal.ScopedCallback; import com.google.javascript.jscomp.graph.GraphNode; import com.google.javascript.jscomp.graph.GraphReachability; import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.TernaryValue; /** * Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user * about unreachable code. * */ class CheckUnreachableCode implements ScopedCallback { static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error( "JSC_UNREACHABLE_CODE", "unreachable code"); private final AbstractCompiler compiler; private final CheckLevel level; CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) { this.compiler = compiler; this.level = level; } @Override public void enterScope(NodeTraversal t) { initScope(t.getControlFlowGraph()); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n); if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) { // Only report error when there are

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> some line number informations. // There are synthetic nodes with no line number informations, nodes // introduce by other passes (although not likely since this pass should // be executed early) or some rhino bug. if (n.getLineno() != -1 && // Allow spurious semi-colons and spurious breaks. !n.isEmpty() && !n.isBreak()) { compiler.report(t.makeError(n, level, UNREACHABLE_CODE)); // From now on, we are going to assume the user fixed the error and not // give more warning related to code section reachable from this node. new GraphReachability<Node, ControlFlowGraph.Branch>( t.getControlFlowGraph()).recompute(n); // Saves time by not traversing children. return false; } } return true; } private void initScope(ControlFlowGraph<Node> controlFlowGraph) { new GraphReachability<Node, ControlFlowGraph.Branch>( controlFlowGraph, new ReachablePredicate()).compute( controlFlowGraph.getEntry().getValue()); } @Override public void exitScope(NodeTraversal t) { } @Override public void visit(NodeTraversal t, Node n, Node parent) { } private final class ReachablePredicate implements Predicate<EdgeTuple<Node, ControlFlowGraph.Branch>> { @Override public boolean apply(EdgeTuple<Node, Branch> input) { Branch branch = input.edge; if (!branch.isConditional()) { return true; } Node predecessor = input.sourceNode; Node condition = NodeUtil.getConditionExpression(predecessor); // TODO(user): Handle more complicated expression like true == true, // etc.... if (condition != null) { TernaryValue val = NodeUtil.getImpureBooleanValue(condition); if (val != TernaryValue.UNKNOWN) { return val.toBoolean(true) == (branch == Branch.ON_TRUE); } } return true; } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2012 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Checks for common errors, such as misplaced semicolons: * <pre> * if (x); act_now(); * </pre> * or comparison against NaN: * <pre> * if (x === NaN) act(); * </pre> * and generates warnings. * * @author johnlenz@google.com (John Lenz) */ final class CheckSuspiciousCode extends AbstractPostOrderCallback { static final DiagnosticType SUSPICIOUS_SEMICOLON = DiagnosticType.warning( "JSC_SUSPICIOUS_SEMICOLON", "If this if/for/while really shouldn't have a body, use {}"); static final DiagnosticType SUSPICIOUS_COMPARISON_WITH_NAN = DiagnosticType.warning( "JSC_SUSPICIOUS_NAN", "Comparison again NaN is always false. Did you mean isNaN()?"); CheckSuspiciousCode() { } @Override public void visit(NodeTraversal t, Node n, Node parent) { checkMissingSemicolon(t, n); checkNaN(t, n); } private void checkMissingSemicolon(NodeTraversal t, Node n) { switch (n.getType()) { case Token.IF: Node true

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Case = n.getFirstChild().getNext(); reportIfWasEmpty(t, trueCase); Node elseCase = trueCase.getNext(); if (elseCase != null) { reportIfWasEmpty(t, elseCase); } break; case Token.WHILE: case Token.FOR: reportIfWasEmpty(t, NodeUtil.getLoopCodeBlock(n)); break; } } private void reportIfWasEmpty(NodeTraversal t, Node block) { Preconditions.checkState(block.isBlock()); // A semicolon is distinguished from a block without children by // annotating it with EMPTY_BLOCK. Blocks without children are // usually intentional, especially with loops. if (!block.hasChildren() && block.wasEmptyNode()) { t.getCompiler().report( t.makeError(block, SUSPICIOUS_SEMICOLON)); } } private void checkNaN(NodeTraversal t, Node n) { switch (n.getType()) { case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LT: case Token.NE: case Token.SHEQ: case Token.SHNE: reportIfNaN(t, n.getFirstChild()); reportIfNaN(t, n.getLastChild()); } } private void reportIfNaN(NodeTraversal t, Node n) { if (NodeUtil.isNaN(n)) { t.getCompiler().report( t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN)); } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> new DepsFinder(); Node root = getAstRoot(compiler); if (root == null) { return; } finder.visitTree(getAstRoot(compiler)); // TODO(nicksantos|user): This caching behavior is a bit // odd, and only works if you assume the exact call flow that // clients are currently using. In that flow, they call // getProvides(), then remove the goog.provide calls from the // AST, and then call getProvides() again. // // This won't work for any other call flow, or any sort of incremental // compilation scheme. The API needs to be fixed so callers aren't // doing weird things like this, and then we should get rid of the // multiple-scan strategy. provides.addAll(finder.provides); requires.addAll(finder.requires); } else { // Otherwise, look at the source code. if (!generatedDependencyInfoFromSource) { // Note: it's OK to use getName() instead of // getPathRelativeToClosureBase() here because we're not using // this to generate deps files. (We're only using it for // symbol dependencies.) DependencyInfo info = (new JsFileParser(compiler.getErrorManager())) .setIncludeGoogBase(true) .parseFile(getName(), getName(), getCode()); provides.addAll(info.getProvides()); requires.addAll(info.getRequires()); generatedDependencyInfoFromSource = true; } } } private static class DepsFinder { private final List<String> provides = Lists.newArrayList(); private final List<String> requires = Lists.newArrayList(); private final CodingConvention codingConvention = new ClosureCodingConvention(); void visitTree(Node n) { visitSubtree(n, null); } void visitSubtree(Node n, Node parent) { if (n.isCall()) { String require = codingConvention.extractClassNameIfRequire(n, parent); if (require != null) { requires.add(require); } String provide = codingConvention.extractClassNameIfProvide(n, parent); if (provide != null) { provides.add(provide); } return; } else if (parent != null && !parent.isExpr

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Result() && !parent.isScript()) { return; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { visitSubtree(child, n); } } } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end * of the file. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public String getLine(int lineNumber) { return getSourceFile().getLine(lineNumber); } /** * Get a region around the indicated line number. The exact definition of a * region is implementation specific, but it must contain the line indicated * by the line number. A region must not start or end by a carriage return. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public Region getRegion(int lineNumber) { return getSourceFile().getRegion(lineNumber); } public String getCode() throws IOException { return getSourceFile().getCode(); } /** Returns the module to which the input belongs. */ public JSModule getModule() { return module; } /** Sets the module to which the input belongs. */ public void setModule(JSModule module) { // An input may only belong to one module. Preconditions.checkArgument( module == null || this.module == null || this.module == module); this.module = module; } /** Overrides the module to which the input belongs. */ void overrideModule(JSModule module) { this.module = module; } public boolean isExtern() { if (ast == null || ast.getSourceFile() == null) { return false; } return ast.getSourceFile().isExtern(); } void setIsExtern(boolean isExtern) { if (ast == null || ast.getSourceFile() == null) { return; } ast.getSourceFile().

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>.jstype.EnumElementType; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSTypeNative; import com.google.javascript.rhino.jstype.JSTypeRegistry; import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.jstype.StaticSlot; import com.google.javascript.rhino.jstype.TemplateType; import com.google.javascript.rhino.jstype.TemplatizedType; import com.google.javascript.rhino.jstype.UnionType; import com.google.javascript.rhino.jstype.Visitor; /** * Chainable reverse abstract interpreter providing basic functionality. * */ public abstract class ChainableReverseAbstractInterpreter implements ReverseAbstractInterpreter { protected final CodingConvention convention; final JSTypeRegistry typeRegistry; private ChainableReverseAbstractInterpreter firstLink; private ChainableReverseAbstractInterpreter nextLink; /** * Constructs an interpreter, which is the only link in a chain. Interpreters * can be appended using {@link #append}. */ public ChainableReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { Preconditions.checkNotNull(convention); this.convention = convention; this.typeRegistry = typeRegistry; firstLink = this; nextLink = null; } /** * Appends a link to {@code this}, returning the updated last link. * <p> * The pattern {@code new X().append(new Y())...append(new Z())} forms a * chain starting with X, then Y, then ... Z. * @param lastLink a chainable interpreter, with no next link * @return the updated last link */ public ChainableReverseAbstractInterpreter append( ChainableReverseAbstractInterpreter lastLink) { Preconditions.checkArgument(lastLink.nextLink == null); this.nextLink = lastLink; lastLink.firstLink = this.firstLink; return lastLink; } /** * Gets the first link of this chain. */ public ChainableReverseAbstractInterpreter getFirst() { return

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Filters warnings based on in-code {@code @suppress} annotations. * @author nicksantos@google.com (Nick Santos) */ class SuppressDocWarningsGuard extends WarningsGuard { private static final long serialVersionUID = 1L; /** Warnings guards for each suppressible warnings group, indexed by name. */ private final Map<String, DiagnosticGroupWarningsGuard> suppressors = Maps.newHashMap(); /** * The suppressible groups, indexed by name. */ SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) { for (Map.Entry<String, DiagnosticGroup> entry : suppressibleGroups.entrySet()) { suppressors.put( entry.getKey(), new DiagnosticGroupWarningsGuard( entry.getValue(), CheckLevel.OFF)); } } @Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { boolean visitedFunction = false; for (Node current = node; current != null; current = current.getParent()) { int type = current.getType(); JSDocInfo info = null; if (type == Token.FUNCTION) { info = NodeUtil.getBestJSDocInfo(current);

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> visitedFunction = true; } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } else if (current.isVar() || current.isAssign()) { // There's one edge case we're worried about: // if the warning points to an assigment to a function, we // want the suppressions on that function to apply. // It's OK if we double-count some cases. Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild()); if (rhs != null) { if (rhs.isCast()) { rhs = rhs.getFirstChild(); } if (rhs.isFunction() && !visitedFunction) { info = NodeUtil.getBestJSDocInfo(current); } } } if (info != null) { for (String suppressor : info.getSuppressions()) { WarningsGuard guard = suppressors.get(suppressor); // Some @suppress tags are for other tools, and // may not have a warnings guard. if (guard != null) { CheckLevel newLevel = guard.level(error); if (newLevel != null) { return newLevel; } } } } } } return null; } @Override public int getPriority() { // Happens after path-based filtering, but before other times // of filtering. return WarningsGuard.Priority.SUPPRESS_DOC.value; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>WithStatement(WithStatement statementNode); abstract T processIllegalToken(AstNode node); public T process(AstNode node) { switch (node.getType()) { case Token.ADD: case Token.AND: case Token.BITAND: case Token.BITOR: case Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.IN: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.OR: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return processInfixExpression((InfixExpression) node); case Token.ARRAYLIT: return processArrayLiteral((ArrayLiteral) node); case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_BITAND: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_DIV: case Token.ASSIGN_LSH: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_RSH: case Token.ASSIGN_SUB: case Token.ASSIGN_URSH: return processAssignment((Assignment) node); case Token.BITNOT: case Token.DEC: case Token.DELPROP: case Token.INC: case Token.NEG: case Token.NOT: case Token.POS: case Token.TYPEOF: case Token.VOID: return processUnaryExpression((UnaryExpression) node); case Token.BLOCK: if (node instanceof Block) { return processBlock((Block) node); } else if (node instanceof Scope) { return processScope((Scope) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.BREAK: return processBreakStatement((BreakStatement) node); case Token.CALL: return

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>ARGEST_BASIC_LATIN = 0x7f; /** the set of builtin constructors that don't have side effects. */ private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS = new HashSet<String>(Arrays.asList( "Array", "Date", "Error", "Object", "RegExp", "XMLHttpRequest")); // Utility class; do not instantiate. private NodeUtil() {} /** * Gets the boolean value of a node that represents a expression. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. * Note: unlike getBooleanValue this function does not return UNKNOWN * for expressions with side-effects. */ static TernaryValue getImpureBooleanValue(Node n) { switch (n.getType()) { case Token.ASSIGN: case Token.COMMA: // For ASSIGN and COMMA the value is the value of the RHS. return getImpureBooleanValue(n.getLastChild()); case Token.NOT: TernaryValue value = getImpureBooleanValue(n.getLastChild()); return value.not(); case Token.AND: { TernaryValue lhs = getImpureBooleanValue(n.getFirstChild()); TernaryValue rhs = getImpureBooleanValue(n.getLastChild()); return lhs.and(rhs); } case Token.OR: { TernaryValue lhs = getImpureBooleanValue(n.getFirstChild()); TernaryValue rhs = getImpureBooleanValue(n.getLastChild()); return lhs.or(rhs); } case Token.HOOK: { TernaryValue trueValue = getImpureBooleanValue( n.getFirstChild().getNext()); TernaryValue falseValue = getImpureBooleanValue(n.getLastChild()); if (trueValue.equals(falseValue)) { return trueValue; } else { return TernaryValue.UNKNOWN; } } case Token.ARRAYLIT: case Token.OBJECTLIT: // ignoring side-effects return TernaryValue.TRUE; case Token.VOID: return TernaryValue.FALSE; default: return getPureBooleanValue(n); } } /** * Gets the boolean value of a node that represents a literal

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>) == Character.SPACE_SEPARATOR) ? TernaryValue.TRUE : TernaryValue.FALSE; } } /** * Gets the function's name. This method recognizes five forms: * <ul> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * In two last cases with named function expressions, the second name is * returned (the variable of qualified name). * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getFunctionName(Node n) { Preconditions.checkState(n.isFunction()); Node parent = n.getParent(); switch (parent.getType()) { case Token.NAME: // var name = function() ... // var name2 = function name1() ... return parent.getQualifiedName(); case Token.ASSIGN: // qualified.name = function() ... // qualified.name2 = function name1() ... return parent.getFirstChild().getQualifiedName(); default: // function name() ... String name = n.getFirstChild().getQualifiedName(); return name; } } /** * Gets the function's name. This method recognizes the forms: * <ul> * <li>{@code &#123;'name': function() ...&#125;}</li> * <li>{@code &#123;name: function() ...&#125;}</li> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * * @param n a node whose type is {@link Token#FUNCTION} * @

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>return the function's name, or {@code null} if it has no name */ public static String getNearestFunctionName(Node n) { if (!n.isFunction()) { return null; } String name = getFunctionName(n); if (name != null) { return name; } // Check for the form { 'x' : function() { } } Node parent = n.getParent(); switch (parent.getType()) { case Token.SETTER_DEF: case Token.GETTER_DEF: case Token.STRING_KEY: // Return the name of the literal's key. return parent.getString(); case Token.NUMBER: return getStringValue(parent); } return null; } /** * Returns true if this is an immutable value. */ static boolean isImmutableValue(Node n) { switch (n.getType()) { case Token.STRING: case Token.NUMBER: case Token.NULL: case Token.TRUE: case Token.FALSE: return true; case Token.CAST: case Token.NOT: return isImmutableValue(n.getFirstChild()); case Token.VOID: case Token.NEG: return isImmutableValue(n.getFirstChild()); case Token.NAME: String name = n.getString(); // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return "undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name); } return false; } /** * Returns true if the operator on this node is symmetric */ static boolean isSymmetricOperation(Node n) { switch (n.getType()) { case Token.EQ: // equal case Token.NE: // not equal case Token.SHEQ: // exactly equal case Token.SHNE: // exactly not equal case Token.MUL: // multiply, unlike add it only works on numbers // or results NaN if any of the operators is not a number return true; } return false; } /** * Returns true if the operator on this node is relational. * the returned set does not include the equalities. */ static boolean isRelationalOperation(Node n) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> child.getNext()) { if (!isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.OBJECTLIT: // Return true only if all values are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child.getFirstChild(), includeFunctions)) { return false; } } return true; case Token.FUNCTION: return includeFunctions && !NodeUtil.isFunctionDeclaration(n); default: return isImmutableValue(n); } } /** * Determines whether the given value may be assigned to a define. * * @param val The value being assigned. * @param defines The list of names of existing defines. */ static boolean isValidDefineValue(Node val, Set<String> defines) { switch (val.getType()) { case Token.STRING: case Token.NUMBER: case Token.TRUE: case Token.FALSE: return true; // Binary operators are only valid if both children are valid. case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return isValidDefineValue(val.getFirstChild(), defines) && isValidDefineValue(val.getLastChild(), defines); // Unary operators are valid if the child is valid. case Token.NOT: case Token.NEG: case Token.POS: return isValidDefineValue(val.getFirstChild(), defines); // Names are valid if and only if they are defines themselves. case Token.NAME: case Token.GETPROP: if (val.isQualifiedName()) { return defines.contains(val.getQualifiedName()); } } return false; } /** * Returns whether this a BLOCK node with

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> no children. * * @param block The node. */ static boolean isEmptyBlock(Node block) { if (!block.isBlock()) { return false; } for (Node n = block.getFirstChild(); n != null; n = n.getNext()) { if (!n.isEmpty()) { return false; } } return true; } static boolean isSimpleOperator(Node n) { return isSimpleOperatorType(n.getType()); } /** * A "simple" operator is one whose children are expressions, * has no direct side-effects (unlike '+='), and has no * conditional aspects (unlike '||'). */ static boolean isSimpleOperatorType(int type) { switch (type) { case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GETELEM: case Token.GETPROP: case Token.GT: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.NOT: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.TYPEOF: case Token.VOID: case Token.POS: case Token.NEG: case Token.URSH: return true; default: return false; } } /** * Creates an EXPR_RESULT. * * @param child The expression itself. * @return Newly created EXPR node with the child as subexpression. */ static Node newExpr(Node child) { return IR.exprResult(child).srcref(child); } /** * Returns true if the node may create new mutable state, or change existing * state. * * @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a> */ static boolean mayEffectMutableState(Node n) { return may

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>EffectMutableState(n, null); } static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, true, compiler); } /** * Returns true if the node which may have side effects when executed. */ static boolean mayHaveSideEffects(Node n) { return mayHaveSideEffects(n, null); } static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, false, compiler); } /** * Returns true if some node in n's subtree changes application state. * If {@code checkForNewObjects} is true, we assume that newly created * mutable objects (like object literals) change state. Otherwise, we assume * that they have no side effects. */ private static boolean checkForStateChangeHelper( Node n, boolean checkForNewObjects, AbstractCompiler compiler) { // Rather than id which ops may have side effects, id the ones // that we know to be safe switch (n.getType()) { // other side-effect free statements and expressions case Token.CAST: case Token.AND: case Token.BLOCK: case Token.EXPR_RESULT: case Token.HOOK: case Token.IF: case Token.IN: case Token.PARAM_LIST: case Token.NUMBER: case Token.OR: case Token.THIS: case Token.TRUE: case Token.FALSE: case Token.NULL: case Token.STRING: case Token.STRING_KEY: case Token.SWITCH: case Token.TRY: case Token.EMPTY: break; // Throws are by definition side effects case Token.THROW: return true; case Token.OBJECTLIT: if (checkForNewObjects) { return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper( c.getFirstChild(), checkForNewObjects, compiler)) { return true; } } return false; case Token.ARRAYLIT: case Token.REGEXP: if (checkForNewObjects) { return true; } break; case Token.VAR: // empty var statement (no declaration

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> * 10 bitwise shift << >> >>> * 11 addition/subtraction + - * 12 multiply/divide * / % * 13 negation/increment ! ~ - ++ -- * 14 call, member () [] . */ static int precedence(int type) { int precedence = precedenceWithDefault(type); if (precedence != -1) { return precedence; } throw new Error("Unknown precedence for " + Token.name(type) + " (type " + type + ")"); } static int precedenceWithDefault(int type) { switch (type) { case Token.COMMA: return 0; case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN: return 1; case Token.HOOK: return 2; // ?: operator case Token.OR: return 3; case Token.AND: return 4; case Token.BITOR: return 5; case Token.BITXOR: return 6; case Token.BITAND: return 7; case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: return 8; case Token.LT: case Token.GT: case Token.LE: case Token.GE: case Token.INSTANCEOF: case Token.IN: return 9; case Token.LSH: case Token.RSH: case Token.URSH: return 10; case Token.SUB: case Token.ADD: return 11; case Token.MUL: case Token.MOD: case Token.DIV: return 12; case Token.INC: case Token.DEC: case Token.NEW: case Token.DELPROP: case Token.TYPEOF: case Token.VOID: case Token.NOT:

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> case Token.BITNOT: case Token.POS: case Token.NEG: return 13; case Token.CALL: case Token.GETELEM: case Token.GETPROP: // Data values case Token.ARRAYLIT: case Token.EMPTY: // TODO(johnlenz): remove this. case Token.FALSE: case Token.FUNCTION: case Token.NAME: case Token.NULL: case Token.NUMBER: case Token.OBJECTLIT: case Token.REGEXP: case Token.STRING: case Token.STRING_KEY: case Token.THIS: case Token.TRUE: return 15; case Token.CAST: return 16; default: // Statements are lower precedence than expressions. return -1; } } static boolean isUndefined(Node n) { switch (n.getType()) { case Token.VOID: return true; case Token.NAME: return n.getString().equals("undefined"); } return false; } static boolean isNullOrUndefined(Node n) { return n.isNull() || isUndefined(n); } static final Predicate<Node> IMMUTABLE_PREDICATE = new Predicate<Node>() { @Override public boolean apply(Node n) { return isImmutableValue(n); } }; static boolean isImmutableResult(Node n) { return allResultsMatch(n, IMMUTABLE_PREDICATE); } /** * Apply the supplied predicate against * all possible result Nodes of the expression. */ static boolean allResultsMatch(Node n, Predicate<Node> p) { switch (n.getType()) { case Token.CAST: return allResultsMatch(n.getFirstChild(), p); case Token.ASSIGN: case Token.COMMA: return allResultsMatch(n.getLastChild(), p); case Token.AND: case Token.OR: return allResultsMatch(n.getFirstChild(), p) && allResultsMatch(n.getLastChild(), p); case Token.HOOK: return allResultsMatch(n.getFirstChild().getNext(), p) && allResultsMatch(n.getLastChild(), p); default: return p.apply(n

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>); } } /** * Apply the supplied predicate against * all possible result Nodes of the expression. */ static boolean anyResultsMatch(Node n, Predicate<Node> p) { switch (n.getType()) { case Token.CAST: return anyResultsMatch(n.getFirstChild(), p); case Token.ASSIGN: case Token.COMMA: return anyResultsMatch(n.getLastChild(), p); case Token.AND: case Token.OR: return anyResultsMatch(n.getFirstChild(), p) || anyResultsMatch(n.getLastChild(), p); case Token.HOOK: return anyResultsMatch(n.getFirstChild().getNext(), p) || anyResultsMatch(n.getLastChild(), p); default: return p.apply(n); } } static class NumbericResultPredicate implements Predicate<Node> { @Override public boolean apply(Node n) { return isNumericResultHelper(n); } } static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE = new NumbericResultPredicate(); /** * Returns true if the result of node evaluation is always a number */ static boolean isNumericResult(Node n) { return allResultsMatch(n, NUMBERIC_RESULT_PREDICATE); } static boolean isNumericResultHelper(Node n) { switch (n.getType()) { case Token.ADD: return !mayBeString(n.getFirstChild()) && !mayBeString(n.getLastChild()); case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.LSH: case Token.RSH: case Token.URSH: case Token.SUB: case Token.MUL: case Token.MOD: case Token.DIV: case Token.INC: case Token.DEC: case Token.POS: case Token.NEG: case Token.NUMBER: return true; case Token.NAME: String name = n.getString(); if (name.equals("NaN")) { return true; } if (name.equals("Infinity")) { return true; } return false; default: return false; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 */ static boolean isAssociative(int type) { switch (type) { case Token.MUL: case Token.AND: case Token.OR: case Token.BITOR: case Token.BITXOR: case Token.BITAND: return true; default: return false; } } /** * Returns true if the operator is commutative. * e.g. (a * b) * c = c * (b * a) * Note 1: "+" is not commutative because it is also the concatenation * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 * Note 2: only operations on literals and pure functions are commutative. */ static boolean isCommutative(int type) { switch (type) { case Token.MUL: case Token.BITOR: case Token.BITXOR: case Token.BITAND: return true; default: return false; } } static boolean isAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: return true; } return false; } static int getOpFromAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN_BITOR: return Token.BITOR; case Token.ASSIGN_BITXOR: return Token.BITXOR; case Token.ASSIGN_BITAND: return Token.BITAND; case Token.ASSIGN_LSH: return Token.LSH; case Token.ASSIGN_RSH: return Token.RSH; case Token.ASSIGN_URSH: return Token.UR

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>SH; case Token.ASSIGN_ADD: return Token.ADD; case Token.ASSIGN_SUB: return Token.SUB; case Token.ASSIGN_MUL: return Token.MUL; case Token.ASSIGN_DIV: return Token.DIV; case Token.ASSIGN_MOD: return Token.MOD; } throw new IllegalArgumentException("Not an assignment op:" + n); } /** * Determines if the given node contains a function statement or function * expression. */ static boolean containsFunction(Node n) { return containsType(n, Token.FUNCTION); } /** * Returns true if the shallow scope contains references to 'this' keyword */ static boolean referencesThis(Node n) { Node start = (n.isFunction()) ? n.getLastChild() : n; return containsType(start, Token.THIS, MATCH_NOT_FUNCTION); } /** * Is this a GETPROP or GETELEM node? */ static boolean isGet(Node n) { return n.isGetProp() || n.isGetElem(); } /** * Is this node the name of a variable being declared? * * @param n The node * @return True if {@code n} is NAME and {@code parent} is VAR */ static boolean isVarDeclaration(Node n) { // There is no need to verify that parent != null because a NAME node // always has a parent in a valid parse tree. return n.isName() && n.getParent().isVar(); } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value. */ static Node getAssignedValue(Node n) { Preconditions.checkState(n.isName()); Node parent = n.getParent(); if (parent.isVar()) { return n.getFirstChild(); } else if (parent.isAssign() && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> static boolean isExprAssign(Node n) { return n.isExprResult() && n.getFirstChild().isAssign(); } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.isExprResult() && n.getFirstChild().isCall(); } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.isFor() && n.getChildCount() == 3; } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE, or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * @return Whether the specified node has a loop parent that * is within the current scope. */ static boolean isWithinLoop(Node n) { for (Node parent : n.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (parent.isFunction()) { break; } } return false; } /** * Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node. */ static boolean isControlStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.WITH: case Token.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>IF: case Token.LABEL: case Token.TRY: case Token.CATCH: case Token.SWITCH: case Token.CASE: case Token.DEFAULT_CASE: return true; default: return false; } } /** * Determines whether the given node is code node for FOR, DO, * WHILE, WITH, or IF node. */ static boolean isControlStructureCodeBlock(Node parent, Node n) { switch (parent.getType()) { case Token.FOR: case Token.WHILE: case Token.LABEL: case Token.WITH: return parent.getLastChild() == n; case Token.DO: return parent.getFirstChild() == n; case Token.IF: return parent.getFirstChild() != n; case Token.TRY: return parent.getFirstChild() == n || parent.getLastChild() == n; case Token.CATCH: return parent.getLastChild() == n; case Token.SWITCH: case Token.CASE: return parent.getFirstChild() != n; case Token.DEFAULT_CASE: return true; default: Preconditions.checkState(isControlStructure(parent)); return false; } } /** * Gets the condition of an ON_TRUE / ON_FALSE CFG edge. * @param n a node with an outgoing conditional CFG edge * @return the condition node or null if the condition is not obviously a node */ static Node getConditionExpression(Node n) { switch (n.getType()) { case Token.IF: case Token.WHILE: return n.getFirstChild(); case Token.DO: return n.getLastChild(); case Token.FOR: switch (n.getChildCount()) { case 3: return null; case 4: return n.getFirstChild().getNext(); } throw new IllegalArgumentException("malformed 'for' statement " + n); case Token.CASE: return null; } throw new IllegalArgumentException(n + " does not have a condition."); } /** * @return Whether the node is of a type that contain other statements. */ static boolean isStatementBlock(Node n) { return n.isScript() || n.isBlock(); } /** *

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> @return Whether the node is used as a statement. */ static boolean isStatement(Node n) { return isStatementParent(n.getParent()); } static boolean isStatementParent(Node parent) { // It is not possible to determine definitely if a node is a statement // or not if it is not part of the AST. A FUNCTION node can be // either part of an expression or a statement. Preconditions.checkState(parent != null); switch (parent.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: return true; default: return false; } } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.isCase() || n.isDefaultCase(); } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty function expression name). */ static boolean isReferenceName(Node n) { return n.isName() && !n.getString().isEmpty(); } /** Whether the child node is the FINALLY block of a try. */ static boolean isTryFinallyNode(Node parent, Node child) { return parent.isTry() && parent.getChildCount() == 3 && child == parent.getLastChild(); } /** Whether the node is a CATCH container BLOCK. */ static boolean isTryCatchNodeContainer(Node n) { Node parent = n.getParent(); return parent.isTry() && parent.getFirstChild().getNext() == n; } /** Safely remove children while maintaining a valid node structure. */ static void removeChild(Node parent, Node node) { if (isTryFinallyNode(parent, node)) { if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) { // A finally can only be removed if there is a catch. parent.removeChild(node); } else { // Otherwise, only its children can be removed. node.detachChildren(); } } else if (node.isCatch()) { // The CATCH can can only be removed if there is a finally clause. Node tryNode = node.getParent().getParent(); Preconditions.checkState(NodeUtil.hasFinally

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>(tryNode)); node.detachFromParent(); } else if (isTryCatchNodeContainer(node)) { // The container node itself can't be removed, but the contained CATCH // can if there is a 'finally' clause Node tryNode = node.getParent(); Preconditions.checkState(NodeUtil.hasFinally(tryNode)); node.detachChildren(); } else if (node.isBlock()) { // Simply empty the block. This maintains source location and // "synthetic"-ness. node.detachChildren(); } else if (isStatementBlock(parent) || isSwitchCase(node)) { // A statement in a block can simply be removed. parent.removeChild(node); } else if (parent.isVar()) { if (parent.hasMoreThanOneChild()) { parent.removeChild(node); } else { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // This would leave an empty VAR, remove the VAR itself. removeChild(parent.getParent(), parent); } } else if (parent.isLabel() && node == parent.getLastChild()) { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // A LABEL without children can not be referred to, remove it. removeChild(parent.getParent(), parent); } else if (parent.isFor() && parent.getChildCount() == 4) { // Only Token.FOR can have an Token.EMPTY other control structure // need something for the condition. Others need to be replaced // or the structure removed. parent.replaceChild(node, IR.empty()); } else { throw new IllegalStateException("Invalid attempt to remove node: " + node.toString() + " of " + parent.toString()); } } /** * Add a finally block if one does not exist. */ static void maybeAddFinally(Node tryNode) { Preconditions.checkState(tryNode.isTry()); if (!NodeUtil.hasFinally(tryNode)) { tryNode.addChildrenToBack(IR.block().srcref(tryNode)); } } /** * Merge a block with its parent block. * @return Whether the block was removed.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> */ static boolean tryMergeBlock(Node block) { Preconditions.checkState(block.isBlock()); Node parent = block.getParent(); // Try to remove the block if its parent is a block/script or if its // parent is label and it has exactly one child. if (isStatementBlock(parent)) { Node previous = block; while (block.hasChildren()) { Node child = block.removeFirstChild(); parent.addChildAfter(child, previous); previous = child; } parent.removeChild(block); return true; } else { return false; } } /** * @param node A node * @return Whether the call is a NEW or CALL node. */ static boolean isCallOrNew(Node node) { return node.isCall() || node.isNew(); } /** * Return a BLOCK node for the given FUNCTION node. */ static Node getFunctionBody(Node fn) { Preconditions.checkArgument(fn.isFunction()); return fn.getLastChild(); } /** * Is this node a function declaration? A function declaration is a function * that has a name that is added to the current scope (i.e. a function that * is not part of a expression; see {@link #isFunctionExpression}). */ static boolean isFunctionDeclaration(Node n) { return n.isFunction() && isStatement(n); } /** * Is this node a hoisted function declaration? A function declaration in the * scope root is hoisted to the top of the scope. * See {@link #isFunctionDeclaration}). */ static boolean isHoistedFunctionDeclaration(Node n) { return isFunctionDeclaration(n) && (n.getParent().isScript() || n.getParent().getParent().isFunction()); } /** * Is a FUNCTION node an function expression? An function expression is one * that has either no name or a name that is not added to the current scope. * * <p>Some examples of function expressions: * <pre> * (function () {}) * (function f() {})() * [ function f() {} ] * var f = function f() {}; * for (function f() {};;) {} * </pre> * * <p>Some

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> examples of functions that are <em>not</em> expressions: * <pre> * function f() {} * if (x); else function f() {} * for (;;) { function f() {} } * </pre> * * @param n A node * @return Whether n is an function used within an expression. */ static boolean isFunctionExpression(Node n) { return n.isFunction() && !isStatement(n); } /** * Returns whether this is a bleeding function (an anonymous named function * that bleeds into the inner scope). */ static boolean isBleedingFunctionName(Node n) { return n.isName() && !n.getString().isEmpty() && isFunctionExpression(n.getParent()); } /** * Determines if a node is a function expression that has an empty body. * * @param node a node * @return whether the given node is a function expression that is empty */ static boolean isEmptyFunctionExpression(Node node) { return isFunctionExpression(node) && isEmptyBlock(node.getLastChild()); } /** * Determines if a function takes a variable number of arguments by * looking for references to the "arguments" var_args object. */ static boolean isVarArgsFunction(Node function) { // TODO(johnlenz): rename this function Preconditions.checkArgument(function.isFunction()); return isNameReferenced( function.getLastChild(), "arguments", MATCH_NOT_FUNCTION); } /** * @return Whether node is a call to methodName. * a.f(...) * a['f'](...) */ static boolean isObjectCallMethod(Node callNode, String methodName) { if (callNode.isCall()) { Node functionIndentifyingExpression = callNode.getFirstChild(); if (isGet(functionIndentifyingExpression)) { Node last = functionIndentifyingExpression.getLastChild(); if (last != null && last.isString()) { String propName = last.getString(); return (propName.equals(methodName)); } } } return false; } /** * @return Whether the callNode represents an expression in the form of: * x.call(...) * x['call'](...) */

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> static boolean isFunctionObjectCall(Node callNode) { return isObjectCallMethod(callNode, "call"); } /** * @return Whether the callNode represents an expression in the form of: * x.apply(...) * x['apply'](...) */ static boolean isFunctionObjectApply(Node callNode) { return isObjectCallMethod(callNode, "apply"); } /** * Determines whether this node is strictly on the left hand side of an assign * or var initialization. Notably, this does not include all L-values, only * statements where the node is used only as an L-value. * * @param n The node * @param parent Parent of the node * @return True if n is the left hand of an assign */ static boolean isVarOrSimpleAssignLhs(Node n, Node parent) { return (parent.isAssign() && parent.getFirstChild() == n) || parent.isVar(); } /** * Determines whether this node is used as an L-value. Notice that sometimes * names are used as both L-values and R-values. * * We treat "var x;" as a pseudo-L-value, which kind of makes sense if you * treat it as "assignment to 'undefined' at the top of the scope". But if * we're honest with ourselves, it doesn't make sense, and we only do this * because it makes sense to treat this as syntactically similar to * "var x = 0;". * * @param n The node * @return True if n is an L-value. */ public static boolean isLValue(Node n) { Preconditions.checkArgument(n.isName() || n.isGetProp() || n.isGetElem()); Node parent = n.getParent(); if (parent == null) { return false; } return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) || (NodeUtil.isForIn(parent) && parent.getFirstChild() == n) || parent.isVar() || (parent.isFunction() && parent.getFirstChild() == n) || parent.isDec() || parent.isInc() ||

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> parent.isParamList() || parent.isCatch(); } /** * Determines whether a node represents an object literal key * (e.g. key1 in {key1: value1, key2: value2}). * * @param node A node */ static boolean isObjectLitKey(Node node) { switch (node.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return true; } return false; } /** * Get the name of an object literal key. * * @param key A node */ static String getObjectLitKeyName(Node key) { switch (key.getType()) { case Token.STRING_KEY: case Token.GETTER_DEF: case Token.SETTER_DEF: return key.getString(); } throw new IllegalStateException("Unexpected node type: " + key); } /** * @param key A OBJECTLIT key node. * @return The type expected when using the key. */ static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) { if (valueType != null) { switch (key.getType()) { case Token.GETTER_DEF: // GET must always return a function type. if (valueType.isFunctionType()) { FunctionType fntype = valueType.toMaybeFunctionType(); valueType = fntype.getReturnType(); } else { return null; } break; case Token.SETTER_DEF: if (valueType.isFunctionType()) { // SET must always return a function type. FunctionType fntype = valueType.toMaybeFunctionType(); Node param = fntype.getParametersNode().getFirstChild(); // SET function must always have one parameter. valueType = param.getJSType(); } else { return null; } break; } } return valueType; } /** * Determines whether a node represents an object literal get or set key * (e.g. key1 in {get key1() {}, set key2(a){}). * * @param node A node */ static boolean isGetOrSetKey(Node node) { switch (node.getType

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>()) { case Token.GETTER_DEF: case Token.SETTER_DEF: return true; } return false; } /** * Converts an operator's token value (see {@link Token}) to a string * representation. * * @param operator the operator's token value to convert * @return the string representation or {@code null} if the token value is * not an operator */ static String opToStr(int operator) { switch (operator) { case Token.BITOR: return "|"; case Token.OR: return "||"; case Token.BITXOR: return "^"; case Token.AND: return "&&"; case Token.BITAND: return "&"; case Token.SHEQ: return "==="; case Token.EQ: return "=="; case Token.NOT: return "!"; case Token.NE: return "!="; case Token.SHNE: return "!=="; case Token.LSH: return "<<"; case Token.IN: return "in"; case Token.LE: return "<="; case Token.LT: return "<"; case Token.URSH: return ">>>"; case Token.RSH: return ">>"; case Token.GE: return ">="; case Token.GT: return ">"; case Token.MUL: return "*"; case Token.DIV: return "/"; case Token.MOD: return "%"; case Token.BITNOT: return "~"; case Token.ADD: return "+"; case Token.SUB: return "-"; case Token.POS: return "+"; case Token.NEG: return "-"; case Token.ASSIGN: return "="; case Token.ASSIGN_BITOR: return "|="; case Token.ASSIGN_BITXOR: return "^="; case Token.ASSIGN_BITAND: return "&="; case Token.ASSIGN_LSH: return "<<="; case Token.ASSIGN_RSH: return ">>="; case Token.ASSIGN_URSH: return ">>>="; case Token.ASSIGN_ADD: return "+="; case Token.ASSIGN_SUB: return "-="; case Token.ASSIGN_MUL: return "*="; case Token.ASSIGN_DIV: return "/="; case Token.ASSIGN_

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>MOD: return "%="; case Token.VOID: return "void"; case Token.TYPEOF: return "typeof"; case Token.INSTANCEOF: return "instanceof"; default: return null; } } /** * Converts an operator's token value (see {@link Token}) to a string * representation or fails. * * @param operator the operator's token value to convert * @return the string representation * @throws Error if the token value is not an operator */ static String opToStrNoFail(int operator) { String res = opToStr(operator); if (res == null) { throw new Error("Unknown op " + operator + ": " + Token.name(operator)); } return res; } /** * @return true if n or any of its children are of the specified type */ static boolean containsType(Node node, int type, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNodeType(type), traverseChildrenPred); } /** * @return true if n or any of its children are of the specified type */ static boolean containsType(Node node, int type) { return containsType(node, type, Predicates.<Node>alwaysTrue()); } /** * Given a node tree, finds all the VAR declarations in that tree that are * not in an inner scope. Then adds a new VAR node at the top of the current * scope that redeclares them, if necessary. */ static void redeclareVarsInsideBranch(Node branch) { Collection<Node> vars = getVarsDeclaredInBranch(branch); if (vars.isEmpty()) { return; } Node parent = getAddingRoot(branch); for (Node nameNode : vars) { Node var = IR.var( IR.name(nameNode.getString()) .srcref(nameNode)) .srcref(nameNode); copyNameAnnotations(nameNode, var.getFirstChild()); parent.addChildToFront(var); } } /** * Copy any annotations that follow a named value. * @param source * @param destination */ static void copyNameAnnotations(Node source, Node destination) { if (source.getBooleanProp(Node.IS

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>_CONSTANT_NAME)) { destination.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } /** * Gets a Node at the top of the current scope where we can add new var * declarations as children. */ private static Node getAddingRoot(Node n) { Node addingRoot = null; Node ancestor = n; while (null != (ancestor = ancestor.getParent())) { int type = ancestor.getType(); if (type == Token.SCRIPT) { addingRoot = ancestor; break; } else if (type == Token.FUNCTION) { addingRoot = ancestor.getLastChild(); break; } } // make sure that the adding root looks ok Preconditions.checkState(addingRoot.isBlock() || addingRoot.isScript()); Preconditions.checkState(addingRoot.getFirstChild() == null || !addingRoot.getFirstChild().isScript()); return addingRoot; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @return A NAME or GETPROP node */ public static Node newQualifiedNameNode( CodingConvention convention, String name) { int endPos = name.indexOf('.'); if (endPos == -1) { return newName(convention, name); } Node node; String nodeName = name.substring(0, endPos); if ("this".equals(nodeName)) { node = IR.thisNode(); } else { node = newName(convention, nodeName); } int startPos; do { startPos = endPos + 1; endPos = name.indexOf('.', startPos); String part = (endPos == -1 ? name.substring(startPos) : name.substring(startPos, endPos)); Node propNode = IR.string(part); if (convention.isConstantKey(part)) { propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } node = IR.getprop(node, propNode); } while (endPos != -1); return node; } /** * Creates a node representing a qualified name. * * @param name A

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> string to be checked for ASCII-goodness. * * @return True if all characters in the string are in Basic Latin set. */ static boolean isLatin(String s) { int len = s.length(); for (int index = 0; index < len; index++) { char c = s.charAt(index); if (c > LARGEST_BASIC_LATIN) { return false; } } return true; } /** * Determines whether the given name is a valid variable name. */ static boolean isValidSimpleName(String name) { return TokenStream.isJSIdentifier(name) && !TokenStream.isKeyword(name) && // no Unicode escaped characters - some browsers are less tolerant // of Unicode characters that might be valid according to the // language spec. // Note that by this point, Unicode escapes have been converted // to UTF-16 characters, so we're only searching for character // values, not escapes. isLatin(name); } /** * Determines whether the given name is a valid qualified name. */ // TODO(nicksantos): This should be moved into a "Language" API, // so that the results are different for es5 and es3. public static boolean isValidQualifiedName(String name) { if (name.endsWith(".") || name.startsWith(".")) { return false; } String[] parts = name.split("\\."); for (String part : parts) { if (!isValidSimpleName(part)) { return false; } } return true; } /** * Determines whether the given name can appear on the right side of * the dot operator. Many properties (like reserved words) cannot. */ static boolean isValidPropertyName(String name) { return isValidSimpleName(name); } private static class VarCollector implements Visitor { final Map<String, Node> vars = Maps.newLinkedHashMap(); @Override public void visit(Node n) { if (n.isName()) { Node parent = n.getParent(); if (parent != null && parent.isVar()) { String name = n.getString(); if (!vars.containsKey(name)) { vars.put(name, n); }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Preconditions.checkState(value.getNext() == null); nodeName.addChildToBack(value); nodeName.srcref(value); } Node var = IR.var(nodeName).srcref(nodeName); return var; } /** * A predicate for matching name nodes with the specified node. */ private static class MatchNameNode implements Predicate<Node>{ final String name; MatchNameNode(String name){ this.name = name; } @Override public boolean apply(Node n) { return n.isName() && n.getString().equals(name); } } /** * A predicate for matching nodes with the specified type. */ static class MatchNodeType implements Predicate<Node>{ final int type; MatchNodeType(int type){ this.type = type; } @Override public boolean apply(Node n) { return n.getType() == type; } } /** * A predicate for matching var or function declarations. */ static class MatchDeclaration implements Predicate<Node> { @Override public boolean apply(Node n) { return isFunctionDeclaration(n) || n.isVar(); } } /** * A predicate for matching anything except function nodes. */ private static class MatchNotFunction implements Predicate<Node>{ @Override public boolean apply(Node n) { return !n.isFunction(); } } static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction(); /** * A predicate for matching statements without exiting the current scope. */ static class MatchShallowStatement implements Predicate<Node>{ @Override public boolean apply(Node n) { Node parent = n.getParent(); return n.isBlock() || (!n.isFunction() && (parent == null || isControlStructure(parent) || isStatementBlock(parent))); } } /** * Finds the number of times a type is referenced within the node tree. */ static int getNodeTypeReferenceCount( Node node, int type, Predicate<Node> traverseChildrenPred) { return getCount(node, new MatchNodeType(type), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { visitPreOrder(c, visitor, traverseChildrenPred); } } } /** * A post-order traversal, calling Visitor.visit for each child matching * the predicate. */ static void visitPostOrder(Node node, Visitor visitor, Predicate<Node> traverseChildrenPred) { if (traverseChildrenPred.apply(node)) { for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { visitPostOrder(c, visitor, traverseChildrenPred); } } visitor.visit(node); } /** * @return Whether a TRY node has a finally block. */ static boolean hasFinally(Node n) { Preconditions.checkArgument(n.isTry()); return n.getChildCount() == 3; } /** * @return The BLOCK node containing the CATCH node (if any) * of a TRY. */ static Node getCatchBlock(Node n) { Preconditions.checkArgument(n.isTry()); return n.getFirstChild().getNext(); } /** * @return Whether BLOCK (from a TRY node) contains a CATCH. * @see NodeUtil#getCatchBlock */ static boolean hasCatchHandler(Node n) { Preconditions.checkArgument(n.isBlock()); return n.hasChildren() && n.getFirstChild().isCatch(); } /** * @param fnNode The function. * @return The Node containing the Function parameters. */ public static Node getFunctionParameters(Node fnNode) { // Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ] Preconditions.checkArgument(fnNode.isFunction()); return fnNode.getFirstChild().getNext(); } /** * <p>Determines whether a variable is constant: * <ol> * <li>In Normalize, any name that matches the * {@link CodingConvention#isConstant(String)} is annotated with an * IS_CONSTANT_NAME property. * </ol> * * @param node A NAME or STRING node * @return True if a name node represents a constant variable */ static boolean is

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>ConstantName(Node node) { return node.getBooleanProp(Node.IS_CONSTANT_NAME); } /** Whether the given name is constant by coding convention. */ static boolean isConstantByConvention( CodingConvention convention, Node node, Node parent) { if (parent.isGetProp() && node == parent.getLastChild()) { return convention.isConstantKey(node.getString()); } else if (isObjectLitKey(node)) { return convention.isConstantKey(node.getString()); } else if (node.isName()) { return convention.isConstant(node.getString()); } return false; } /** * Get the JSDocInfo for a function. */ public static JSDocInfo getFunctionJSDocInfo(Node n) { Preconditions.checkState(n.isFunction()); JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null && NodeUtil.isFunctionExpression(n)) { // Look for the info on other nodes. Node parent = n.getParent(); if (parent.isAssign()) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.isName()) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } return fnInfo; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = n.getSourceFileName(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The source name property on the node or its ancestors. */ public static StaticSourceFile getSourceFile(Node n) { StaticSourceFile sourceName = null; while (sourceName == null && n != null) { sourceName = n.getStaticSourceFile(); n = n.getParent(); } return sourceName; } /** * @param n The node. * @return The InputId property on the node or its ancestors.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> */ public static InputId getInputId(Node n) { while (n != null && !n.isScript()) { n = n.getParent(); } return (n != null && n.isScript()) ? n.getInputId() : null; } /** * A new CALL node with the "FREE_CALL" set based on call target. */ static Node newCallNode(Node callTarget, Node... parameters) { boolean isFreeCall = !isGet(callTarget); Node call = IR.call(callTarget); call.putBooleanProp(Node.FREE_CALL, isFreeCall); for (Node parameter : parameters) { call.addChildToBack(parameter); } return call; } /** * @return Whether the node is known to be a value that is not referenced * elsewhere. */ static boolean evaluatesToLocalValue(Node value) { return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse()); } /** * @param locals A predicate to apply to unknown local values. * @return Whether the node is known to be a value that is not a reference * outside the expression scope. */ static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) { switch (value.getType()) { case Token.CAST: return evaluatesToLocalValue(value.getFirstChild(), locals); case Token.ASSIGN: // A result that is aliased by a non-local name, is the effectively the // same as returning a non-local name, but this doesn't matter if the // value is immutable. return NodeUtil.isImmutableValue(value.getLastChild()) || (locals.apply(value) && evaluatesToLocalValue(value.getLastChild(), locals)); case Token.COMMA: return evaluatesToLocalValue(value.getLastChild(), locals); case Token.AND: case Token.OR: return evaluatesToLocalValue(value.getFirstChild(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.HOOK: return evaluatesToLocalValue(value.getFirstChild().getNext(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.INC: case Token.DEC: if (value.getBooleanProp(Node.INCRDE

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>CR_PROP)) { return evaluatesToLocalValue(value.getFirstChild(), locals); } else { return true; } case Token.THIS: return locals.apply(value); case Token.NAME: return isImmutableValue(value) || locals.apply(value); case Token.GETELEM: case Token.GETPROP: // There is no information about the locality of object properties. return locals.apply(value); case Token.CALL: return callHasLocalResult(value) || isToStringMethodCall(value) || locals.apply(value); case Token.NEW: return newHasLocalResult(value) || locals.apply(value); case Token.FUNCTION: case Token.REGEXP: case Token.ARRAYLIT: case Token.OBJECTLIT: // Literals objects with non-literal children are allowed. return true; case Token.DELPROP: case Token.IN: // TODO(johnlenz): should IN operator be included in #isSimpleOperator? return true; default: // Other op force a local value: // x = '' + g (x is now an local string) // x -= g (x is now an local number) if (isAssignmentOp(value) || isSimpleOperator(value) || isImmutableValue(value)) { return true; } throw new IllegalStateException( "Unexpected expression node" + value + "\n parent:" + value.getParent()); } } /** * Given the first sibling, this returns the nth * sibling or null if no such sibling exists. * This is like "getChildAtIndex" but returns null for non-existent indexes. */ private static Node getNthSibling(Node first, int index) { Node sibling = first; while (index != 0 && sibling != null) { sibling = sibling.getNext(); index--; } return sibling; } /** * Given the function, this returns the nth * argument or null if no such parameter exists. */ static Node getArgumentForFunction(Node function, int index) { Preconditions.checkState(function.isFunction()); return getNthSibling( function.getFirstChild().getNext().getFirstChild(), index); } /** * Given

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> the new or call, this returns the nth * argument of the call or null if no such argument exists. */ static Node getArgumentForCallOrNew(Node call, int index) { Preconditions.checkState(isCallOrNew(call)); return getNthSibling( call.getFirstChild().getNext(), index); } /** * Returns whether this is a target of a call or new. */ static boolean isCallOrNewTarget(Node target) { Node parent = target.getParent(); return parent != null && NodeUtil.isCallOrNew(parent) && parent.getFirstChild() == target; } private static boolean isToStringMethodCall(Node call) { Node getNode = call.getFirstChild(); if (isGet(getNode)) { Node propNode = getNode.getLastChild(); return propNode.isString() && "toString".equals(propNode.getString()); } return false; } /** Find the best JSDoc for the given node. */ static JSDocInfo getBestJSDocInfo(Node n) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { Node parent = n.getParent(); if (parent == null) { return null; } if (parent.isName()) { return getBestJSDocInfo(parent); } else if (parent.isAssign()) { return getBestJSDocInfo(parent); } else if (isObjectLitKey(parent)) { return parent.getJSDocInfo(); } else if (parent.isFunction()) { return parent.getJSDocInfo(); } else if (parent.isVar() && parent.hasOneChild()) { return parent.getJSDocInfo(); } else if ((parent.isHook() && parent.getFirstChild() != n) || parent.isOr() || parent.isAnd() || (parent.isComma() && parent.getFirstChild() != n)) { return getBestJSDocInfo(parent); } else if (parent.isCast()) { return parent.getJSDocInfo(); } } return info; } /** Find the l-value that the given r-value is being assigned to. */ static

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Node getBestLValue(Node n) { Node parent = n.getParent(); boolean isFunctionDeclaration = isFunctionDeclaration(n); if (isFunctionDeclaration) { return n.getFirstChild(); } else if (parent.isName()) { return parent; } else if (parent.isAssign()) { return parent.getFirstChild(); } else if (isObjectLitKey(parent)) { return parent; } else if ( (parent.isHook() && parent.getFirstChild() != n) || parent.isOr() || parent.isAnd() || (parent.isComma() && parent.getFirstChild() != n)) { return getBestLValue(parent); } else if (parent.isCast()) { return getBestLValue(parent); } return null; } /** Gets the r-value of a node returned by getBestLValue. */ static Node getRValueOfLValue(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.ASSIGN: return n.getNext(); case Token.VAR: return n.getFirstChild(); case Token.FUNCTION: return parent; } return null; } /** Get the owner of the given l-value node. */ static Node getBestLValueOwner(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue)) { return getBestLValue(lValue.getParent()); } else if (isGet(lValue)) { return lValue.getFirstChild(); } return null; } /** Get the name of the given l-value node. */ static String getBestLValueName(@Nullable Node lValue) { if (lValue == null || lValue.getParent() == null) { return null; } if (isObjectLitKey(lValue)) { Node owner = getBestLValue(lValue.getParent()); if (owner != null) { String ownerName = getBestLValueName(owner); if (ownerName != null) { return ownerName + "." + getObjectLitKeyName(lValue); } } return null; } return

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> lValue.getQualifiedName(); } /** * @returns false iff the result of the expression is not consumed. */ static boolean isExpressionResultUsed(Node expr) { // TODO(johnlenz): consider sharing some code with trySimpleUnusedResult. Node parent = expr.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.EXPR_RESULT: return false; case Token.CAST: return isExpressionResultUsed(parent); case Token.HOOK: case Token.AND: case Token.OR: return (expr == parent.getFirstChild()) ? true : isExpressionResultUsed(parent); case Token.COMMA: Node gramps = parent.getParent(); if (gramps.isCall() && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first // expression to a comma to be a no-op if it's used to indirect // an eval. This we pretend that this is "used". if (expr == parent.getFirstChild() && parent.getChildCount() == 2 && expr.getNext().isName() && "eval".equals(expr.getNext().getString())) { return true; } } return (expr == parent.getFirstChild()) ? false : isExpressionResultUsed(parent); case Token.FOR: if (!NodeUtil.isForIn(parent)) { // Only an expression whose result is in the condition part of the // expression is used. return (parent.getChildAtIndex(1) == expr); } break; } return true; } /** * @param n The expression to check. * @return Whether the expression is unconditionally executed only once in the * containing execution scope. */ static boolean isExecutedExactlyOnce(Node n) { inspect: do { Node parent = n.getParent(); switch (parent.getType()) { case Token.IF: case Token.HOOK: case Token.AND: case Token.OR: if (parent.getFirstChild() != n) { return false;

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> } // other ancestors may be conditional continue inspect; case Token.FOR: if (NodeUtil.isForIn(parent)) { if (parent.getChildAtIndex(1) != n) { return false; } } else { if (parent.getFirstChild() != n) { return false; } } // other ancestors may be conditional continue inspect; case Token.WHILE: case Token.DO: return false; case Token.TRY: // Consider all code under a try/catch to be conditionally executed. if (!hasFinally(parent) || parent.getLastChild() != n) { return false; } continue inspect; case Token.CASE: case Token.DEFAULT_CASE: return false; case Token.SCRIPT: case Token.FUNCTION: // Done, we've reached the scope root. break inspect; } } while ((n = n.getParent()) != null); return true; } /** * @return An appropriate AST node for the boolean value. */ static Node booleanNode(boolean value) { return value ? IR.trueNode() : IR.falseNode(); } /** * @return An appropriate AST node for the double value. */ static Node numberNode(double value, Node srcref) { Node result; if (Double.isNaN(value)) { result = IR.name("NaN"); } else if (value == Double.POSITIVE_INFINITY) { result = IR.name("Infinity"); } else if (value == Double.NEGATIVE_INFINITY) { result = IR.neg(IR.name("Infinity")); } else { result = IR.number(value); } if (srcref != null) { result.srcrefTree(srcref); } return result; } static boolean isNaN(Node n) { if ((n.isName() && n.getString().equals("NaN")) || (n.getType() == Token.DIV && n.getFirstChild().isNumber() && n.getFirstChild().getDouble() == 0 && n.getLastChild().isNumber() && n.getLastChild().getDouble() == 0)) { return true; } return false; } /** * Given

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> maps m1 * and m2 is the map of the union of names with {@link JSType#getLeastSupertype} * to meet the m1 type and m2 type. * * @see NodeTraversal * @see DataFlowAnalysis * */ public class Scope implements StaticScope<JSType>, StaticSymbolTable<Scope.Var, Scope.Var> { private final Map<String, Var> vars = new LinkedHashMap<String, Var>(); private final Scope parent; private final int depth; private final Node rootNode; /** Whether this is a bottom scope for the purposes of type inference. */ private final boolean isBottom; private Var arguments; private static final Predicate<Var> DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES = new Predicate<Var>() { @Override public boolean apply(Var var) { return var.getParentNode() != null && var.getType() == null && // no declared type var.getParentNode().isVar() && !var.isExtern(); } }; /** Stores info about a variable */ public static class Var implements StaticSlot<JSType>, StaticReference<JSType> { /** name */ final String name; /** Var node */ final Node nameNode; /** * The variable's type. */ private JSType type; /** * Whether the variable's type has been inferred or is declared. An inferred * type may change over time (as more code is discovered), whereas a * declared type is a static contract that must be matched. */ private final boolean typeInferred; /** Input source */ final CompilerInput input; /** * The index at which the var is declared. e..g if it's 0, it's the first * declared variable in that scope */ final int index; /** The enclosing scope */ final Scope scope; /** @see #isMarkedEscaped */ private boolean markedEscaped = false; /** @see #isMarkedAssignedExactlyOnce */ private boolean markedAssignedExactlyOnce = false; /** * Creates a variable. * * @param inferred whether its type is inferred (as opposed to declared) */ private Var(boolean inferred, String name, Node nameNode, JSType type, Scope scope,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> int index, CompilerInput input) { this.name = name; this.nameNode = nameNode; this.type = type; this.scope = scope; this.index = index; this.input = input; this.typeInferred = inferred; } /** * Gets the name of the variable. */ @Override public String getName() { return name; } /** * Gets the node for the name of the variable. */ @Override public Node getNode() { return nameNode; } CompilerInput getInput() { return input; } @Override public StaticSourceFile getSourceFile() { return nameNode.getStaticSourceFile(); } @Override public Var getSymbol() { return this; } @Override public Var getDeclaration() { return nameNode == null ? null : this; } /** * Gets the parent of the name node. */ public Node getParentNode() { return nameNode == null ? null : nameNode.getParent(); } /** * Whether this is a bleeding function (an anonymous named function * that bleeds into the inner scope). */ public boolean isBleedingFunction() { return NodeUtil.isFunctionExpression(getParentNode()); } /** * Gets the scope where this variable is declared. */ Scope getScope() { return scope; } /** * Returns whether this is a global variable. */ public boolean isGlobal() { return scope.isGlobal(); } /** * Returns whether this is a local variable. */ public boolean isLocal() { return scope.isLocal(); } /** * Returns whether this is defined in an extern file. */ boolean isExtern() { return input == null || input.isExtern(); } /** * Returns {@code true} if the variable is declared as a constant, * based on the value reported by {@code NodeUtil}. */ public boolean isConst() { return nameNode != null && NodeUtil.isConstantName(nameNode); } /** * Returns {@code true} if the variable is declared as a define. * A variable is a define if it is annotated by {@code @define}. */ public

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> by an inner scope. * * In other words, it's assigned in an inner scope so that it's much harder * to make assertions about its value at a given point. */ void markEscaped() { markedEscaped = true; } /** * Whether this is escaped by an inner scope. * Notice that not all scope creators record this information. */ boolean isMarkedEscaped() { return markedEscaped; } /** * Record that this is assigned exactly once.. * * In other words, it's assigned in an inner scope so that it's much harder * to make assertions about its value at a given point. */ void markAssignedExactlyOnce() { markedAssignedExactlyOnce = true; } /** * Whether this is assigned exactly once. * Notice that not all scope creators record this information. */ boolean isMarkedAssignedExactlyOnce() { return markedAssignedExactlyOnce; } } /** * A special subclass of Var used to distinguish "arguments" in the current * scope. */ // TODO(johnlenz): Include this the list of Vars for the scope. public static class Arguments extends Var { Arguments(Scope scope) { super( false, // no inferred "arguments", // always arguments null, // no declaration node // TODO(johnlenz): provide the type of "Arguments". null, // no type info scope, -1, // no variable index null // input ); } @Override public boolean equals(Object other) { if (!(other instanceof Arguments)) { return false; } Arguments otherVar = (Arguments) other; return otherVar.scope.getRootNode() == scope.getRootNode(); } @Override public int hashCode() { return System.identityHashCode(this); } } /** * Creates a Scope given the parent Scope and the root node of the scope. * @param parent The parent Scope. Cannot be null. * @param rootNode Typically the FUNCTION node. */ Scope(Scope parent, Node rootNode) { Preconditions.checkNotNull(parent); Preconditions.checkArgument(rootNode != parent.rootNode); this.parent = parent; this.rootNode = rootNode;

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> this.isBottom = false; this.depth = parent.depth + 1; } /** * Creates a empty Scope (bottom of the lattice). * @param rootNode Typically a FUNCTION node or the global BLOCK node. * @param isBottom Whether this is the bottom of a lattice. Otherwise, * it must be a global scope. */ private Scope(Node rootNode, boolean isBottom) { this.parent = null; this.rootNode = rootNode; this.isBottom = isBottom; this.depth = 0; } static Scope createGlobalScope(Node rootNode) { return new Scope(rootNode, false); } static Scope createLatticeBottom(Node rootNode) { return new Scope(rootNode, true); } /** The depth of the scope. The global scope has depth 0. */ int getDepth() { return depth; } /** Whether this is the bottom of the lattice. */ boolean isBottom() { return isBottom; } /** * Gets the container node of the scope. This is typically the FUNCTION * node or the global BLOCK/SCRIPT node. */ @Override public Node getRootNode() { return rootNode; } public Scope getParent() { return parent; } Scope getGlobalScope() { Scope result = this; while (result.getParent() != null) { result = result.getParent(); } return result; } @Override public StaticScope<JSType> getParentScope() { return parent; } /** * Gets the type of {@code this} in the current scope. */ @Override public JSType getTypeOfThis() { if (isGlobal()) { return ObjectType.cast(rootNode.getJSType()); } Preconditions.checkState(rootNode.isFunction()); JSType nodeType = rootNode.getJSType(); if (nodeType != null && nodeType.isFunctionType()) { return nodeType.toMaybeFunctionType().getTypeOfThis(); } else { return parent.getTypeOfThis(); } } /** * Declares a variable whose type is inferred. * * @param name name of the variable * @param nameNode the NAME node declaring the variable

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> * @param type the variable's type * @param input the input in which this variable is defined. */ Var declare(String name, Node nameNode, JSType type, CompilerInput input) { return declare(name, nameNode, type, input, true); } /** * Declares a variable. * * @param name name of the variable * @param nameNode the NAME node declaring the variable * @param type the variable's type * @param input the input in which this variable is defined. * @param inferred Whether this variable's type is inferred (as opposed * to declared). */ Var declare(String name, Node nameNode, JSType type, CompilerInput input, boolean inferred) { Preconditions.checkState(name != null && name.length() > 0); // Make sure that it's declared only once Preconditions.checkState(vars.get(name) == null); Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input); vars.put(name, var); return var; } /** * Undeclares a variable, to be used when the compiler optimizes out * a variable and removes it from the scope. */ void undeclare(Var var) { Preconditions.checkState(var.scope == this); Preconditions.checkState(vars.get(var.name) == var); vars.remove(var.name); } @Override public Var getSlot(String name) { return getVar(name); } @Override public Var getOwnSlot(String name) { return vars.get(name); } /** * Returns the variable, may be null */ public Var getVar(String name) { Var var = vars.get(name); if (var != null) { return var; } else if (parent != null) { // Recurse up the parent Scope return parent.getVar(name); } else { return null; } } /** * Get a unique VAR object to represents "arguments" within this scope */ public Var getArgumentsVar() { if (arguments == null) { arguments = new Arguments(this); } return arguments; }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> /** * Returns true if a variable is declared. */ public boolean isDeclared(String name, boolean recurse) { Scope scope = this; if (scope.vars.containsKey(name)) { return true; } if (scope.parent != null && recurse) { return scope.parent.isDeclared(name, recurse); } return false; } /** * Return an iterator over all of the variables declared in this scope. */ public Iterator<Var> getVars() { return vars.values().iterator(); } /** * Return an iterable over all of the variables declared in this scope. */ Iterable<Var> getVarIterable() { return vars.values(); } @Override public Iterable<Var> getReferences(Var var) { return ImmutableList.of(var); } @Override public StaticScope<JSType> getScope(Var var) { return var.scope; } @Override public Iterable<Var> getAllSymbols() { return Collections.unmodifiableCollection(vars.values()); } /** * Returns number of variables in this scope */ public int getVarCount() { return vars.size(); } /** * Returns whether this is the global scope. */ public boolean isGlobal() { return parent == null; } /** * Returns whether this is a local scope (i.e. not the global scope). */ public boolean isLocal() { return parent != null; } /** * Gets all variables declared with "var" but without declared types attached. */ public Iterator<Var> getDeclarativelyUnboundVarsWithoutTypes() { return Iterators.filter( getVars(), DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES); } static interface TypeResolver { void resolveTypes(); } private TypeResolver typeResolver; /** Resolve all type references. Only used on typed scopes. */ void resolveTypes() { if (typeResolver != null) { typeResolver.resolveTypes(); typeResolver = null; } } void setTypeResolver(TypeResolver resolver) { this.typeResolver = resolver; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.Callback; import com.google.javascript.rhino.Node; /** * A simple pass to ensure that all AST nodes have line numbers, * an that the line numbers are monotonically increasing. * * @author nicksantos@google.com (Nick Santos) */ class LineNumberCheck implements Callback, CompilerPass { static final DiagnosticType MISSING_LINE_INFO = DiagnosticType.error( "JSC_MISSING_LINE_INFO", "No source location information associated with {0}.\n" + "Most likely a Node has been created with settings the source file " + "and line/column location. Usually this is done using " + "Node.copyInformationFrom and supplying a Node from the source AST."); private final AbstractCompiler compiler; private boolean requiresLineNumbers = false; LineNumberCheck(AbstractCompiler compiler) { this.compiler = compiler; } public void setCheckSubTree(Node root) { requiresLineNumbers = true; NodeTraversal.traverse(compiler, root, this); } @Override public void process(Node externs, Node root) { requiresLineNumbers = false; NodeTraversal.traverse(compiler, root, this); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { // Each JavaScript file is rooted in a script node, so we'll only // have line number information inside the script node. if (n.isScript()) { requiresLineNumbers

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> = true; } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isScript()) { requiresLineNumbers = false; } else if (requiresLineNumbers) { if (n.getLineno() == -1) { // The tree version of the node is really the best diagnostic // info we have to offer here. compiler.report( t.makeError(n, MISSING_LINE_INFO, n.toStringTree())); } } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Preconditions.checkNotNull(registerFunction); return this.registerFunction == registerFunction; } boolean isGetterFunction() { return registerFunction != null; } String getName() { return name; } String getExpectedTypeName() { return expectedTypeName; } Node createDefaultValueNode() { switch (this) { case REGISTER_BOOLEAN: return IR.falseNode(); case REGISTER_NUMBER: return IR.number(0); case REGISTER_STRING: return IR.string(""); default: throw new IllegalStateException(); } } } // A map of function name -> TweakFunction. private static final Map<String, TweakFunction> TWEAK_FUNCTIONS_MAP; static { TWEAK_FUNCTIONS_MAP = Maps.newHashMap(); for (TweakFunction func : TweakFunction.values()) { TWEAK_FUNCTIONS_MAP.put(func.getName(), func); } } ProcessTweaks(AbstractCompiler compiler, boolean stripTweaks, Map<String, Node> compilerDefaultValueOverrides) { this.compiler = compiler; this.stripTweaks = stripTweaks; // Having the map sorted is required for the unit tests to be deterministic. this.compilerDefaultValueOverrides = Maps.newTreeMap(); this.compilerDefaultValueOverrides.putAll(compilerDefaultValueOverrides); } @Override public void process(Node externs, Node root) { CollectTweaksResult result = collectTweaks(root); applyCompilerDefaultValueOverrides(result.tweakInfos); boolean changed = false; if (stripTweaks) { changed = stripAllCalls(result.tweakInfos); } else if (!compilerDefaultValueOverrides.isEmpty()) { changed = replaceGetCompilerOverridesCalls(result.getOverridesCalls); } if (changed) { compiler.reportCodeChange(); } } /** * Passes the compiler default value overrides to the JS by replacing calls * to goog.tweak.getCompilerOverrids_ with a map of tweak ID->default value; */ private boolean replaceGetCompilerOverridesCalls( List<TweakFunctionCall> calls) { for (TweakFunctionCall call : calls) { Node callNode = call.callNode;

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Node objNode = createCompilerDefaultValueOverridesVarNode(callNode); callNode.getParent().replaceChild(callNode, objNode); } return !calls.isEmpty(); } /** * Removes all CALL nodes in the given TweakInfos, replacing calls to getter * functions with the tweak's default value. */ private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) { for (TweakInfo tweakInfo : tweakInfos.values()) { boolean isRegistered = tweakInfo.isRegistered(); for (TweakFunctionCall functionCall : tweakInfo.functionCalls) { Node callNode = functionCall.callNode; Node parent = callNode.getParent(); if (functionCall.tweakFunc.isGetterFunction()) { Node newValue; if (isRegistered) { newValue = tweakInfo.getDefaultValueNode().cloneNode(); } else { // When we find a getter of an unregistered tweak, there has // already been a warning about it, so now just use a default // value when stripping. TweakFunction registerFunction = functionCall.tweakFunc.registerFunction; newValue = registerFunction.createDefaultValueNode(); } parent.replaceChild(callNode, newValue); } else { Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode)) .srcref(callNode); parent.replaceChild(callNode, voidZeroNode); } } } return !tweakInfos.isEmpty(); } /** * Creates a JS object that holds a map of tweakId -> default value override. */ private Node createCompilerDefaultValueOverridesVarNode( Node sourceInformationNode) { Node objNode = IR.objectlit().srcref(sourceInformationNode); for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) { Node objKeyNode = IR.stringKey(entry.getKey()) .copyInformationFrom(sourceInformationNode); Node objValueNode = entry.getValue().cloneNode() .copyInformationFrom(sourceInformationNode); objKeyNode.addChildToBack(objValueNode); objNode.addChildToBack(objKeyNode); } return objNode; } /** Sets the default values of tweaks based on compiler options. */

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> @SuppressWarnings("incomplete-switch") @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall()) { return; } String callName = n.getFirstChild().getQualifiedName(); TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName); if (tweakFunc == null) { return; } if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) { getOverridesCalls.add( new TweakFunctionCall(t.getSourceName(), tweakFunc, n)); return; } // Ensure the first parameter (the tweak ID) is a string literal. Node tweakIdNode = n.getFirstChild().getNext(); if (!tweakIdNode.isString()) { compiler.report(t.makeError(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR)); return; } String tweakId = tweakIdNode.getString(); // Make sure there is a TweakInfo structure for it. TweakInfo tweakInfo = allTweaks.get(tweakId); if (tweakInfo == null) { tweakInfo = new TweakInfo(tweakId); allTweaks.put(tweakId, tweakInfo); } switch (tweakFunc) { case REGISTER_BOOLEAN: case REGISTER_NUMBER: case REGISTER_STRING: // Ensure the ID contains only valid characters. if (!ID_MATCHER.matchesAllOf(tweakId)) { compiler.report(t.makeError(tweakIdNode, INVALID_TWEAK_ID_ERROR)); } // Ensure tweaks are registered in the global scope. if (!t.inGlobalScope()) { compiler.report( t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId)); break; } // Ensure tweaks are registered only once. if (tweakInfo.isRegistered()) { compiler.report( t.makeError(n, TWEAK_MULTIPLY_REGISTERED_ERROR, tweakId)); break; } Node tweakDefaultValueNode = tweakIdNode.getNext().getNext(); tweakInfo.addRegisterCall(t.getSourceName(), tweakFunc, n, tweakDefaultValue

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.Node; /** * This interface defines how objects capable of creating scopes from the parse * tree behave. * */ interface ScopeCreator { /** * Creates a {@link Scope} object. * * @param n the root node (either a FUNCTION node, a SCRIPT node, or a * synthetic block node whose children are all SCRIPT nodes) * @param parent the parent Scope object (may be null) */ Scope createScope(Node n, Scope parent); }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> args) { compiler.report(JSError.make(sourceName, errorRoot, error, args)); } /** * Determines whether the given JsDoc info declares a function type. */ static boolean isFunctionTypeDeclaration(JSDocInfo info) { return info.getParameterCount() > 0 || info.hasReturnType() || info.hasThisType() || info.isConstructor() || info.isInterface(); } /** * The scope that we should declare this function in, if it needs * to be declared in a scope. Notice that TypedScopeCreator takes * care of most scope-declaring. */ private Scope getScopeDeclaredIn() { int dotIndex = fnName.indexOf("."); if (dotIndex != -1) { String rootVarName = fnName.substring(0, dotIndex); Var rootVar = scope.getVar(rootVarName); if (rootVar != null) { return rootVar.getScope(); } } return scope; } /** * Check whether a type is resolvable in the future * If this has a supertype that hasn't been resolved yet, then we can assume * this type will be OK once the super type resolves. * @param objectType * @return true if objectType is resolvable in the future */ private static boolean hasMoreTagsToResolve(ObjectType objectType) { Preconditions.checkArgument(objectType.isUnknownType()); if (objectType.getImplicitPrototype() != null) { // constructor extends class if (objectType.getImplicitPrototype().isResolved()) { return false; } else { return true; } } else { // interface extends interfaces FunctionType ctor = objectType.getConstructor(); if (ctor != null) { for (ObjectType interfaceType : ctor.getExtendedInterfaces()) { if (!interfaceType.isResolved()) { return true; } } } return false; } } /** Holds data dynamically inferred about functions. */ static interface FunctionContents { /** Returns the source node of this function. May be null. */ Node getSourceNode(); /** Returns if the function may be in externs. */ boolean mayBeFromExterns(); /** Returns if a return of

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> warning level. */ CheckGlobalNames(AbstractCompiler compiler, CheckLevel level) { this.compiler = compiler; this.convention = compiler.getCodingConvention(); this.level = level; } /** * Injects a pre-computed global namespace, so that the same namespace * can be re-used for multiple check passes. Returns this for easy chaining. */ CheckGlobalNames injectNamespace(GlobalNamespace namespace) { Preconditions.checkArgument(namespace.hasExternsRoot()); this.namespace = namespace; return this; } @Override public void process(Node externs, Node root) { if (namespace == null) { namespace = new GlobalNamespace(compiler, externs, root); } // Find prototype properties that will affect our analysis. Preconditions.checkState(namespace.hasExternsRoot()); findPrototypeProps("Object", objectPrototypeProps); findPrototypeProps("Function", functionPrototypeProps); objectPrototypeProps.addAll( convention.getIndirectlyDeclaredProperties()); for (Name name : namespace.getNameForest()) { // Skip extern names. Externs are often not runnable as real code, // and will do things like: // var x; // x.method; // which this check forbids. if (name.inExterns) { continue; } checkDescendantNames(name, name.globalSets + name.localSets > 0); } } private void findPrototypeProps(String type, Set<String> props) { Name slot = namespace.getSlot(type); if (slot != null) { for (Ref ref : slot.getRefs()) { if (ref.type == Ref.Type.PROTOTYPE_GET) { Node fullName = ref.getNode().getParent().getParent(); if (fullName.isGetProp()) { props.add(fullName.getLastChild().getString()); } } } } } /** * Checks to make sure all the descendants of a name are defined if they * are referenced. * * @param name A global name. * @param nameIsDefined If true, {@code name} is defined. Otherwise, it's * undefined, and any references to descendant names should emit warnings. */ private void

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> checkDescendantNames(Name name, boolean nameIsDefined) { if (name.props != null) { for (Name prop : name.props) { // if the ancestor of a property is not defined, then we should emit // warnings for all references to the property. boolean propIsDefined = false; if (nameIsDefined) { // if the ancestor of a property is defined, then let's check that // the property is also explicitly defined if it needs to be. propIsDefined = (!propertyMustBeInitializedByFullName(prop) || prop.globalSets + prop.localSets > 0); } validateName(prop, propIsDefined); checkDescendantNames(prop, propIsDefined); } } } private void validateName(Name name, boolean isDefined) { // If the name is not defined, emit warnings for each reference. While // we're looking through each reference, check all the module dependencies. Ref declaration = name.getDeclaration(); Name parent = name.parent; JSModuleGraph moduleGraph = compiler.getModuleGraph(); for (Ref ref : name.getRefs()) { // Don't worry about global exprs. boolean isGlobalExpr = ref.getNode().getParent().isExprResult(); if (!isDefined && !isTypedef(ref)) { if (!isGlobalExpr) { reportRefToUndefinedName(name, ref); } } else if (declaration != null && ref.getModule() != declaration.getModule() && !moduleGraph.dependsOn( ref.getModule(), declaration.getModule())) { reportBadModuleReference(name, ref); } else { // Check for late references. if (ref.scope.isGlobal()) { // Prototype references are special, because in our reference graph, // A.prototype counts as a reference to A. boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_GET); Name owner = isPrototypeGet ? name : parent; boolean singleGlobalParentDecl = owner != null && owner.getDeclaration() != null && owner.localSets == 0; if (singleGlobalParentDecl && owner.getDeclaration().preOrderIndex > ref.preOrderIndex) { String refName = isPrototypeGet ? name.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>getFullName() + ".prototype" : name.getFullName(); compiler.report( JSError.make(ref.source.getName(), ref.node, NAME_DEFINED_LATE_WARNING, refName, owner.getFullName(), owner.getDeclaration().source.getName(), String.valueOf(owner.getDeclaration().node.getLineno()))); } } } } } private boolean isTypedef(Ref ref) { // If this is an annotated EXPR-GET, don't do anything. Node parent = ref.node.getParent(); if (parent.isExprResult()) { JSDocInfo info = ref.node.getJSDocInfo(); if (info != null && info.hasTypedefType()) { return true; } } return false; } private void reportBadModuleReference(Name name, Ref ref) { compiler.report( JSError.make(ref.source.getName(), ref.node, STRICT_MODULE_DEP_QNAME, ref.getModule().getName(), name.getDeclaration().getModule().getName(), name.getFullName())); } private void reportRefToUndefinedName(Name name, Ref ref) { // grab the highest undefined ancestor to output in the warning message. while (name.parent != null && name.parent.globalSets + name.parent.localSets == 0) { name = name.parent; } compiler.report( JSError.make(ref.getSourceName(), ref.node, level, UNDEFINED_NAME_WARNING, name.getFullName())); } /** * Checks whether the given name is a property, and whether that property * must be initialized with its full qualified name. */ private boolean propertyMustBeInitializedByFullName(Name name) { // If an object or function literal in the global namespace is never // aliased, then its properties can only come from one of 2 places: // 1) From its prototype chain, or // 2) From an assignment to its fully qualified name. // If we assume #1 is not the case, then #2 implies that its // properties must all be modeled in the GlobalNamespace as well. // // We assume that for global object literals and types (constructors and //

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> interfaces), we can find all the properties inherited from the prototype // chain of functions and objects. if (name.parent == null) { return false; } boolean parentIsAliased = false; if (name.parent.aliasingGets > 0) { for (Ref ref : name.parent.getRefs()) { if (ref.type == Ref.Type.ALIASING_GET) { Node aliaser = ref.getNode().getParent(); // We don't need to worry about known aliased, because // they're already covered by the getIndirectlyDeclaredProperties // call at the top. boolean isKnownAlias = aliaser.isCall() && (convention.getClassesDefinedByCall(aliaser) != null || convention.getSingletonGetterClassName(aliaser) != null); if (!isKnownAlias) { parentIsAliased = true; } } } } if (parentIsAliased) { return false; } if (objectPrototypeProps.contains(name.getBaseName())) { return false; } if (name.parent.type == Name.Type.OBJECTLIT) { return true; } if (name.parent.type == Name.Type.FUNCTION && name.parent.isDeclaredType() && !functionPrototypeProps.contains(name.getBaseName())) { return true; } return false; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>String propertyName) { return "superClass_".equals(propertyName) || super.isSuperClassReference(propertyName); } /** * Given a qualified name node, returns whether "prototype" is at the end. * For example: * a.b.c => false * a.b.c.prototype => true */ private boolean endsWithPrototype(Node qualifiedName) { return qualifiedName.isGetProp() && qualifiedName.getLastChild().getString().equals("prototype"); } /** * Extracts X from goog.provide('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfProvide(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.provide"); } /** * Extracts X from goog.require('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfRequire(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.require"); } private static String extractClassNameIfGoog(Node node, Node parent, String functionName){ String className = null; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.isGetProp()) { String qualifiedName = callee.getQualifiedName(); if (functionName.equals(qualifiedName)) { Node target = callee.getNext(); if (target != null && target.isString()) { className = target.getString(); } } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } @Override public List<String> identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if ("

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>goog.addDependency".equals(callName.getQualifiedName()) && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.isArrayLit()) { List<String> typeNames = Lists.newArrayList(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.isString()) { typeNames.add(name.getString()); } } return typeNames; } } return super.identifyTypeDeclarationCall(n); } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } @Override public String getSingletonGetterClassName(Node callNode) { Node callArg = callNode.getFirstChild(); String callName = callArg.getQualifiedName(); // Use both the original name and the post-CollapseProperties name. if (!("goog.addSingletonGetter".equals(callName) || "goog$addSingletonGetter".equals(callName)) || callNode.getChildCount() != 2) { return super.getSingletonGetterClassName(callNode); } return callArg.getNext().getQualifiedName(); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { super.applySingletonGetter(functionType, getterType, objectType); functionType.defineDeclaredProperty("getInstance", getterType, functionType.getSource()); functionType.defineDeclaredProperty("instance_", objectType, functionType.getSource()); } @Override public String getGlobalObject() { return "goog.global"; } private final Set<String> propertyTestFunctions = ImmutableSet.of( "goog.isDef", "goog.isNull", "goog.isDefAndNotNull", "goog.isString", "goog.isNumber", "goog.isBoolean", "goog.isFunction", "goog.isArray", "goog.isObject"); @Override public boolean isPropertyTestFunction(Node call) { Preconditions.checkArgument(call.isCall()); return propertyTestFunctions.contains( call.getFirstChild().getQualifiedName()) || super.isPropertyTestFunction(call); } @Override public ObjectLiteralCast getObjectLiteralCast(Node call

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Node) { Preconditions.checkArgument(callNode.isCall()); ObjectLiteralCast proxyCast = super.getObjectLiteralCast(callNode); if (proxyCast != null) { return proxyCast; } Node callName = callNode.getFirstChild(); if (!"goog.reflect.object".equals(callName.getQualifiedName()) || callNode.getChildCount() != 3) { return null; } Node typeNode = callName.getNext(); if (!typeNode.isQualifiedName()) { return null; } Node objectNode = typeNode.getNext(); if (!objectNode.isObjectLit()) { return new ObjectLiteralCast(null, null, OBJECTLIT_EXPECTED); } return new ObjectLiteralCast( typeNode.getQualifiedName(), typeNode.getNext(), null); } @Override public boolean isOptionalParameter(Node parameter) { return false; } @Override public boolean isVarArgsParameter(Node parameter) { return false; } @Override public boolean isPrivate(String name) { return false; } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return ImmutableList.<AssertionFunctionSpec>of( new AssertionFunctionSpec("goog.asserts.assert"), new AssertionFunctionSpec("goog.asserts.assertNumber", JSTypeNative.NUMBER_TYPE), new AssertionFunctionSpec("goog.asserts.assertString", JSTypeNative.STRING_TYPE), new AssertionFunctionSpec("goog.asserts.assertFunction", JSTypeNative.FUNCTION_INSTANCE_TYPE), new AssertionFunctionSpec("goog.asserts.assertObject", JSTypeNative.OBJECT_TYPE), new AssertionFunctionSpec("goog.asserts.assertArray", JSTypeNative.ARRAY_TYPE), new AssertInstanceofSpec("goog.asserts.assertInstanceof") ); } @Override public Bind describeFunctionBind(Node n, boolean useTypeInfo) { Bind result = super.describeFunctionBind(n, useTypeInfo); if (result != null) { return result; } if (!n.isCall()) { return null; } Node callTarget = n.getFirstChild(); String name = callTarget.getQualifiedName(); if (name != null) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Value = objectValue; } @Override public int getIntValue() { throw new UnsupportedOperationException(); } @Override public Object getObjectValue() { return objectValue; } @Override public String toString() { return objectValue == null ? "null" : objectValue.toString(); } @Override public PropListItem chain(PropListItem next) { return new ObjectPropListItem(getType(), objectValue, next); } } // A base class for int storing props private static class IntPropListItem extends AbstractPropListItem { private static final long serialVersionUID = 1L; final int intValue; IntPropListItem(int propType, int intValue, PropListItem next) { super(propType, next); this.intValue = intValue; } @Override public int getIntValue() { return intValue; } @Override public Object getObjectValue() { throw new UnsupportedOperationException(); } @Override public String toString() { return String.valueOf(intValue); } @Override public PropListItem chain(PropListItem next) { return new IntPropListItem(getType(), intValue, next); } } public Node(int nodeType) { type = nodeType; parent = null; sourcePosition = -1; } public Node(int nodeType, Node child) { Preconditions.checkArgument(child.parent == null, "new child has existing parent"); Preconditions.checkArgument(child.next == null, "new child has existing sibling"); type = nodeType; parent = null; first = last = child; child.next = null; child.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node right) { Preconditions.checkArgument(left.parent == null, "first new child has existing parent"); Preconditions.checkArgument(left.next == null, "first new child has existing sibling"); Preconditions.checkArgument(right.parent == null, "second new child has existing parent"); Preconditions.checkArgument(right.next == null, "second new child has existing sibling"); type = nodeType; parent = null; first = left; last = right; left.next = right

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>; left.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null; first = left; last = right; left.next = mid; left.parent = this; mid.next = right; mid.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, Node left, Node mid, Node mid2, Node right) { Preconditions.checkArgument(left.parent == null); Preconditions.checkArgument(left.next == null); Preconditions.checkArgument(mid.parent == null); Preconditions.checkArgument(mid.next == null); Preconditions.checkArgument(mid2.parent == null); Preconditions.checkArgument(mid2.next == null); Preconditions.checkArgument(right.parent == null); Preconditions.checkArgument(right.next == null); type = nodeType; parent = null; first = left; last = right; left.next = mid; left.parent = this; mid.next = mid2; mid.parent = this; mid2.next = right; mid2.parent = this; right.next = null; right.parent = this; sourcePosition = -1; } public Node(int nodeType, int lineno, int charno) { type = nodeType; parent = null; sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node child, int lineno, int charno) { this(nodeType, child); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node right, int lineno, int charno) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> this(nodeType, left, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node right, int lineno, int charno) { this(nodeType, left, mid, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node mid2, Node right, int lineno, int charno) { this(nodeType, left, mid, mid2, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children, int lineno, int charno) { this(nodeType, children); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children) { this.type = nodeType; parent = null; if (children.length != 0) { this.first = children[0]; this.last = children[children.length - 1]; for (int i = 1; i < children.length; i++) { if (null != children[i - 1].next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } children[i - 1].next = children[i]; Preconditions.checkArgument(children[i - 1].parent == null); children[i - 1].parent = this; } Preconditions.checkArgument(children[children.length - 1].parent == null); children[children.length - 1].parent = this; if (null != this.last.next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } } } public static Node newNumber(double number) { return new NumberNode(number); } public static Node newNumber(double number, int lineno, int charno) { return new NumberNode(number, lineno, charno); } public static Node newString(String str) { return new StringNode(Token.STRING, str); } public static Node newString(int type, String str) { return

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> new StringNode(type, str); } public static Node newString(String str, int lineno, int charno) { return new StringNode(Token.STRING, str, lineno, charno); } public static Node newString(int type, String str, int lineno, int charno) { return new StringNode(type, str, lineno, charno); } public int getType() { return type; } public void setType(int type) { this.type = type; } public boolean hasChildren() { return first != null; } public Node getFirstChild() { return first; } public Node getLastChild() { return last; } public Node getNext() { return next; } public Node getChildBefore(Node child) { if (child == first) { return null; } Node n = first; while (n.next != child) { n = n.next; if (n == null) { throw new RuntimeException("node is not a child"); } } return n; } public Node getChildAtIndex(int i) { Node n = first; while (i > 0) { n = n.next; i--; } return n; } public int getIndexOfChild(Node child) { Node n = first; int i = 0; while (n != null) { if (child == n) { return i; } n = n.next; i++; } return -1; } public Node getLastSibling() { Node n = this; while (n.next != null) { n = n.next; } return n; } public void addChildToFront(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this; child.next = first; first = child; if (last == null) { last = child; } } public void addChildToBack(Node child) { Preconditions.checkArgument(child.parent == null); Preconditions.checkArgument(child.next == null); child.parent = this;

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> child.next = null; if (last == null) { first = last = child; return; } last.next = child; last = child; } public void addChildrenToFront(Node children) { for (Node child = children; child != null; child = child.next) { Preconditions.checkArgument(child.parent == null); child.parent = this; } Node lastSib = children.getLastSibling(); lastSib.next = first; first = children; if (last == null) { last = lastSib; } } public void addChildrenToBack(Node children) { addChildrenAfter(children, getLastChild()); } /** * Add 'child' before 'node'. */ public void addChildBefore(Node newChild, Node node) { Preconditions.checkArgument(node != null && node.parent == this, "The existing child node of the parent should not be null."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); if (first == node) { newChild.parent = this; newChild.next = first; first = newChild; return; } Node prev = getChildBefore(node); addChildAfter(newChild, prev); } /** * Add 'child' after 'node'. */ public void addChildAfter(Node newChild, Node node) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); addChildrenAfter(newChild, node); } /** * Add all children after 'node'. */ public void addChildrenAfter(Node children, Node node) { Preconditions.checkArgument(node == null || node.parent == this); for (Node child = children; child != null; child = child.next) { Preconditions.checkArgument(child.parent == null); child.parent = this; } Node lastSibling = children.getLastSibling(); if (node != null) { Node oldNext = node.next; node.next = children; lastSibling.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>next = oldNext; if (node == last) { last = lastSibling; } } else { // Append to the beginning. if (first != null) { lastSibling.next = first; } else { last = lastSibling; } first = children; } } /** * Detach a child from its parent and siblings. */ public void removeChild(Node child) { Node prev = getChildBefore(child); if (prev == null) { first = first.next; } else { prev.next = child.next; } if (child == last) { last = prev; } child.next = null; child.parent = null; } /** * Detaches child from Node and replaces it with newChild. */ public void replaceChild(Node child, Node newChild) { Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(child); newChild.next = child.next; newChild.parent = this; if (child == first) { first = newChild; } else { Node prev = getChildBefore(child); prev.next = newChild; } if (child == last) { last = newChild; } child.next = null; child.parent = null; } public void replaceChildAfter(Node prevChild, Node newChild) { Preconditions.checkArgument(prevChild.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(newChild.next == null, "The new child node has siblings."); Preconditions.checkArgument(newChild.parent == null, "The new child node already has a parent."); // Copy over important information. newChild.copyInformationFrom(prevChild); Node child = prevChild.next; newChild.next = child.next; newChild.parent = this; prevChild.next = newChild; if (child == last) { last = newChild; }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> child.next = null; child.parent = null; } @VisibleForTesting PropListItem lookupProperty(int propType) { PropListItem x = propListHead; while (x != null && propType != x.getType()) { x = x.getNext(); } return x; } /** * Clone the properties from the provided node without copying * the property object. The receiving node may not have any * existing properties. * @param other The node to clone properties from. * @return this node. */ public Node clonePropsFrom(Node other) { Preconditions.checkState(this.propListHead == null, "Node has existing properties."); this.propListHead = other.propListHead; return this; } public void removeProp(int propType) { PropListItem result = removeProp(propListHead, propType); if (result != propListHead) { propListHead = result; } } /** * @param item The item to inspect * @param propType The property to look for * @return The replacement list if the property was removed, or * 'item' otherwise. */ private PropListItem removeProp(PropListItem item, int propType) { if (item == null) { return null; } else if (item.getType() == propType) { return item.getNext(); } else { PropListItem result = removeProp(item.getNext(), propType); if (result != item.getNext()) { return item.chain(result); } else { return item; } } } public Object getProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return null; } return item.getObjectValue(); } public boolean getBooleanProp(int propType) { return getIntProp(propType) != 0; } /** * Returns the integer value for the property, or 0 if the property * is not defined. */ public int getIntProp(int propType) { PropListItem item = lookupProperty(propType); if (item == null) { return 0; } return item.getIntValue();

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> how many of the lower-order bits of * sourcePosition are reserved for storing the column number. * Bits above these store the line number. * This gives us decent position information for everything except * files already passed through a minimizer, where lines might * be longer than 4096 characters. */ public static final int COLUMN_BITS = 12; /** * MAX_COLUMN_NUMBER represents the maximum column number that can * be represented. JSCompiler's modifications to Rhino cause all * tokens located beyond the maximum column to MAX_COLUMN_NUMBER. */ public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1; /** * COLUMN_MASK stores a value where bits storing the column number * are set, and bits storing the line are not set. It's handy for * separating column number from line number. */ public static final int COLUMN_MASK = MAX_COLUMN_NUMBER; /** * Source position of this node. The position is encoded with the * column number in the low 12 bits of the integer, and the line * number in the rest. Create some handy constants so we can change this * size if we want. */ private int sourcePosition; private JSType jsType; private Node parent; //========================================================================== // Source position management public void setStaticSourceFile(StaticSourceFile file) { this.putProp(STATIC_SOURCE_FILE, file); } /** Sets the source file to a non-extern file of the given name. */ public void setSourceFileForTesting(String name) { this.putProp(STATIC_SOURCE_FILE, new SimpleSourceFile(name, false)); } public String getSourceFileName() { StaticSourceFile file = getStaticSourceFile(); return file == null ? null : file.getName(); } /** Returns the source file associated with this input. May be null */ public StaticSourceFile getStaticSourceFile() { return ((StaticSourceFile) this.getProp(STATIC_SOURCE_FILE)); } /** * @param inputId */ public void setInputId(InputId inputId) { this.putProp(INPUT_ID, inputId); } /** * @return The Id of the CompilerInput associated with this Node.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Iterable<Node>, Iterator<Node> { private final Node start; private Node current; private boolean used; SiblingNodeIterable(Node start) { this.start = start; this.current = start; this.used = false; } @Override public Iterator<Node> iterator() { if (!used) { used = true; return this; } else { // We have already used the current object as an iterator; // we must create a new SiblingNodeIterable based on this // iterable's start node. // // Since the primary use case for Node.children is in for // loops, this branch is extremely unlikely. return (new SiblingNodeIterable(start)).iterator(); } } @Override public boolean hasNext() { return current != null; } @Override public Node next() { if (current == null) { throw new NoSuchElementException(); } try { return current; } finally { current = current.getNext(); } } @Override public void remove() { throw new UnsupportedOperationException(); } } // ========================================================================== // Accessors PropListItem getPropListHeadForTesting() { return propListHead; } public Node getParent() { return parent; } /** * Gets the ancestor node relative to this. * * @param level 0 = this, 1 = the parent, etc. */ public Node getAncestor(int level) { Preconditions.checkArgument(level >= 0); Node node = this; while (node != null && level-- > 0) { node = node.getParent(); } return node; } /** * Iterates all of the node's ancestors excluding itself. */ public AncestorIterable getAncestors() { return new AncestorIterable(this.getParent()); } /** * Iterator to go up the ancestor tree. */ public static class AncestorIterable implements Iterable<Node> { private Node cur; /** * @param cur The node to start. */ AncestorIterable(Node cur) { this.cur = cur; } @Override public Iterator<Node> iterator() { return new Iterator<Node>() { @Override public boolean has

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Next() { return cur != null; } @Override public Node next() { if (!hasNext()) { throw new NoSuchElementException(); } Node n = cur; cur = cur.getParent(); return n; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } /** * Check for one child more efficiently than by iterating over all the * children as is done with Node.getChildCount(). * * @return Whether the node has exactly one child. */ public boolean hasOneChild() { return first != null && first == last; } /** * Check for more than one child more efficiently than by iterating over all * the children as is done with Node.getChildCount(). * * @return Whether the node more than one child. */ public boolean hasMoreThanOneChild() { return first != null && first != last; } public int getChildCount() { int c = 0; for (Node n = first; n != null; n = n.next) { c++; } return c; } // Intended for testing and verification only. public boolean hasChild(Node child) { for (Node n = first; n != null; n = n.getNext()) { if (child == n) { return true; } } return false; } /** * Checks if the subtree under this node is the same as another subtree. * Returns null if it's equal, or a message describing the differences. */ public String checkTreeEquals(Node node2) { NodeMismatch diff = checkTreeEqualsImpl(node2); if (diff != null) { return "Node tree inequality:" + "\nTree1:\n" + toStringTree() + "\n\nTree2:\n" + node2.toStringTree() + "\n\nSubtree1: " + diff.nodeA.toStringTree() + "\n\nSubtree2: " + diff.nodeB.toStringTree(); } return null; } /** * Compare this node to node2 recursively and return the first pair of nodes * that differs doing a preorder depth-first traversal. Package private for * testing.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> by dots. If the node ultimately under the left * sub-tree is not a simple name, this is not a valid qualified name. * * @return a null if this is not a qualified name, or a dot-separated string * of the name and properties. */ public String getQualifiedName() { if (type == Token.NAME) { String name = getString(); return name.isEmpty() ? null : name; } else if (type == Token.GETPROP) { String left = getFirstChild().getQualifiedName(); if (left == null) { return null; } return left + "." + getLastChild().getString(); } else if (type == Token.THIS) { return "this"; } else { return null; } } /** * Returns whether a node corresponds to a simple or a qualified name, such as * <code>x</code> or <code>a.b.c</code> or <code>this.a</code>. */ public boolean isQualifiedName() { switch (getType()) { case Token.NAME: return getString().isEmpty() ? false : true; case Token.THIS: return true; case Token.GETPROP: return getFirstChild().isQualifiedName(); default: return false; } } /** * Returns whether a node corresponds to a simple or a qualified name without * a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code> * . */ public boolean isUnscopedQualifiedName() { switch (getType()) { case Token.NAME: return getString().isEmpty() ? false : true; case Token.GETPROP: return getFirstChild().isUnscopedQualifiedName(); default: return false; } } // ========================================================================== // Mutators /** * Removes this node from its parent. Equivalent to: * node.getParent().removeChild(); */ public Node detachFromParent() { Preconditions.checkState(parent != null); parent.removeChild(this); return this; } /** * Removes the first child of Node. Equivalent to: * node.removeChild(node.getFirstChild()); * * @return The removed Node.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> */ public Node removeFirstChild() { Node child = first; if (child != null) { removeChild(child); } return child; } /** * @return A Node that is the head of the list of children. */ public Node removeChildren() { Node children = first; for (Node child = first; child != null; child = child.getNext()) { child.parent = null; } first = null; last = null; return children; } /** * Removes all children from this node and isolates the children from each * other. */ public void detachChildren() { for (Node child = first; child != null;) { Node nextChild = child.getNext(); child.parent = null; child.next = null; child = nextChild; } first = null; last = null; } public Node removeChildAfter(Node prev) { Preconditions.checkArgument(prev.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(prev.next != null, "no next sibling."); Node child = prev.next; prev.next = child.next; if (child == last) { last = prev; } child.next = null; child.parent = null; return child; } /** * @return A detached clone of the Node, specifically excluding its children. */ public Node cloneNode() { Node result; try { result = (Node) super.clone(); // PropListItem lists are immutable and can be shared so there is no // need to clone them here. result.next = null; result.first = null; result.last = null; result.parent = null; } catch (CloneNotSupportedException e) { throw new RuntimeException(e.getMessage()); } return result; } /** * @return A detached clone of the Node and all its children. */ public Node cloneTree() { Node result = cloneNode(); for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) { Node n2clone = n2.cloneTree(); n2clone.parent = result; if

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> (result.last != null) { result.last.next = n2clone; } if (result.first == null) { result.first = n2clone; } result.last = n2clone; } return result; } /** * Copies source file and name information from the other * node given to the current node. Used for maintaining * debug information across node append and remove operations. * @return this */ // TODO(nicksantos): The semantics of this method are ill-defined. Delete it. public Node copyInformationFrom(Node other) { if (getProp(ORIGINALNAME_PROP) == null) { putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP)); } if (getProp(STATIC_SOURCE_FILE) == null) { putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE)); sourcePosition = other.sourcePosition; } return this; } /** * Copies source file and name information from the other node to the * entire tree rooted at this node. * @return this */ // TODO(nicksantos): The semantics of this method are ill-defined. Delete it. public Node copyInformationFromForTree(Node other) { copyInformationFrom(other); for (Node child = getFirstChild(); child != null; child = child.getNext()) { child.copyInformationFromForTree(other); } return this; } /** * Overwrite all the source information in this node with * that of {@code other}. */ public Node useSourceInfoFrom(Node other) { putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP)); putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE)); sourcePosition = other.sourcePosition; return this; } public Node srcref(Node other) { return useSourceInfoFrom(other); } /** * Overwrite all the source information in this node and its subtree with * that of {@code other}. */ public Node useSourceInfoFromForTree(Node other) { useSourceInfoFrom(other);

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> exception // locality of the result // We want a value of 0 to mean "global state changes and // unknown locality of result". public static final int FLAG_GLOBAL_STATE_UNMODIFIED = 1; public static final int FLAG_THIS_UNMODIFIED = 2; public static final int FLAG_ARGUMENTS_UNMODIFIED = 4; public static final int FLAG_NO_THROWS = 8; public static final int FLAG_LOCAL_RESULTS = 16; public static final int SIDE_EFFECTS_FLAGS_MASK = 31; public static final int SIDE_EFFECTS_ALL = 0; public static final int NO_SIDE_EFFECTS = FLAG_GLOBAL_STATE_UNMODIFIED | FLAG_THIS_UNMODIFIED | FLAG_ARGUMENTS_UNMODIFIED | FLAG_NO_THROWS; /** * Marks this function or constructor call's side effect flags. * This property is only meaningful for {@link Token#CALL} and * {@link Token#NEW} nodes. */ public void setSideEffectFlags(int flags) { Preconditions.checkArgument( getType() == Token.CALL || getType() == Token.NEW, "setIsNoSideEffectsCall only supports CALL and NEW nodes, got " + Token.name(getType())); putIntProp(SIDE_EFFECT_FLAGS, flags); } public void setSideEffectFlags(SideEffectFlags flags) { setSideEffectFlags(flags.valueOf()); } /** * Returns the side effects flags for this node. */ public int getSideEffectFlags() { return getIntProp(SIDE_EFFECT_FLAGS); } /** * A helper class for getting and setting the side-effect flags. * @author johnlenz@google.com (John Lenz) */ public static class SideEffectFlags { private int value = Node.SIDE_EFFECTS_ALL; public SideEffectFlags() { } public SideEffectFlags(int value) { this.value = value; } public int valueOf() { return value; } /** All side-effect occur and the returned results are non-local. */ public SideEffectFlags setAllFlags() { value = Node.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> public int hashCode() { return Objects.hashCode(nodeA, nodeB); } } /*** AST type check methods ***/ public boolean isAdd() { return this.getType() == Token.ADD; } public boolean isAnd() { return this.getType() == Token.AND; } public boolean isArrayLit() { return this.getType() == Token.ARRAYLIT; } public boolean isAssign() { return this.getType() == Token.ASSIGN; } public boolean isAssignAdd() { return this.getType() == Token.ASSIGN_ADD; } public boolean isBlock() { return this.getType() == Token.BLOCK; } public boolean isBreak() { return this.getType() == Token.BREAK; } public boolean isCall() { return this.getType() == Token.CALL; } public boolean isCase() { return this.getType() == Token.CASE; } public boolean isCast() { return this.getType() == Token.CAST; } public boolean isCatch() { return this.getType() == Token.CATCH; } public boolean isComma() { return this.getType() == Token.COMMA; } public boolean isContinue() { return this.getType() == Token.CONTINUE; } public boolean isDebugger() { return this.getType() == Token.DEBUGGER; } public boolean isDec() { return this.getType() == Token.DEC; } public boolean isDefaultCase() { return this.getType() == Token.DEFAULT_CASE; } public boolean isDelProp() { return this.getType() == Token.DELPROP; } public boolean isDo() { return this.getType() == Token.DO; } public boolean isEmpty() { return this.getType() == Token.EMPTY; } public boolean isExprResult() { return this.getType() == Token.EXPR_RESULT; } public boolean isFalse() { return this.getType() == Token.FALSE; } public boolean isFor() { return this.getType() == Token.FOR; } public boolean isFunction() { return this.getType() == Token.FUNCTION; } public boolean isGetterDef

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>() { return this.getType() == Token.GETTER_DEF; } public boolean isGetElem() { return this.getType() == Token.GETELEM; } public boolean isGetProp() { return this.getType() == Token.GETPROP; } public boolean isHook() { return this.getType() == Token.HOOK; } public boolean isIf() { return this.getType() == Token.IF; } public boolean isIn() { return this.getType() == Token.IN; } public boolean isInc() { return this.getType() == Token.INC; } public boolean isInstanceOf() { return this.getType() == Token.INSTANCEOF; } public boolean isLabel() { return this.getType() == Token.LABEL; } public boolean isLabelName() { return this.getType() == Token.LABEL_NAME; } public boolean isName() { return this.getType() == Token.NAME; } public boolean isNE() { return this.getType() == Token.NE; } public boolean isNew() { return this.getType() == Token.NEW; } public boolean isNot() { return this.getType() == Token.NOT; } public boolean isNull() { return this.getType() == Token.NULL; } public boolean isNumber() { return this.getType() == Token.NUMBER; } public boolean isObjectLit() { return this.getType() == Token.OBJECTLIT; } public boolean isOr() { return this.getType() == Token.OR; } public boolean isParamList() { return this.getType() == Token.PARAM_LIST; } public boolean isRegExp() { return this.getType() == Token.REGEXP; } public boolean isReturn() { return this.getType() == Token.RETURN; } public boolean isScript() { return this.getType() == Token.SCRIPT; } public boolean isSetterDef() { return this.getType() == Token.SETTER_DEF; } public boolean isString() { return this.getType() == Token.STRING; } public boolean isStringKey() { return this.getType()

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> private final Map<String, Node> ctors = Maps.newHashMap(); private final CodingConvention convention; CheckProvidesCallback(CodingConvention convention){ this.convention = convention; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: String providedClassName = codingConvention.extractClassNameIfProvide(n, parent); if (providedClassName != null) { provides.put(providedClassName, n); } break; case Token.FUNCTION: visitFunctionNode(n, parent); break; case Token.SCRIPT: visitScriptNode(); } } private void visitFunctionNode(Node n, Node parent) { Node name = null; JSDocInfo info = parent.getJSDocInfo(); if (info != null && info.isConstructor()) { name = parent.getFirstChild(); } else { // look to the child, maybe it's a named function info = n.getJSDocInfo(); if (info != null && info.isConstructor()) { name = n.getFirstChild(); } } if (name != null && name.isQualifiedName()) { String qualifiedName = name.getQualifiedName(); if (!this.convention.isPrivate(qualifiedName)) { Visibility visibility = info.getVisibility(); if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) { ctors.put(qualifiedName, name); } } } } private void visitScriptNode() { for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) { String ctor = ctorEntry.getKey(); int index = -1; boolean found = false; do { index = ctor.indexOf('.', index + 1); String provideKey = index == -1 ? ctor : ctor.substring(0, index); if (provides.containsKey(provideKey)) { found = true; break; } } while (index != -1); if (!found) { Node n = ctorEntry.getValue(); compiler.report( JSError.make(n.getSourceFileName(), n, checkLevel, MISSING_PROVIDE_WARNING, ctorEntry.getKey())); }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> { @Override public void visit(AbstractCompiler compiler, Node root) { if (root.isFunction()) { root = root.getLastChild(); } do { handler.reset(); NodeTraversal.traverse(compiler, root, new PeepCallback()); } while (retraverseOnChange && handler.hasCodeChanged()); } }); endTraversal(); compiler.removeChangeHandler(handler); } private class PeepCallback extends AbstractShallowCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { Node currentNode = n, newNode; boolean codeChanged = false; do { codeChanged = false; for (AbstractPeepholeOptimization optim : peepholeOptimizations) { newNode = optim.optimizeSubtree(currentNode); if (newNode != currentNode) { codeChanged = true; currentNode = newNode; } if (currentNode == null) { return; } } } while(codeChanged); } } /** * Make sure that all the optimizations have the current traversal so they * can report errors. */ private void beginTraversal() { for (AbstractPeepholeOptimization optimization : peepholeOptimizations) { optimization.beginTraversal(compiler); } } private void endTraversal() { for (AbstractPeepholeOptimization optimization : peepholeOptimizations) { optimization.endTraversal(compiler); } } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>_TYPE); registerNativeType(JSTypeNative.GREATEST_FUNCTION_TYPE, GREATEST_FUNCTION_TYPE); // Register the prototype property. See the comments below in // registerPropertyOnType about the bootstrapping process. registerPropertyOnType("prototype", OBJECT_FUNCTION_TYPE); } private void initializeRegistry() { register(getNativeType(JSTypeNative.ARRAY_TYPE)); register(getNativeType(JSTypeNative.BOOLEAN_OBJECT_TYPE)); register(getNativeType(JSTypeNative.BOOLEAN_TYPE)); register(getNativeType(JSTypeNative.DATE_TYPE)); register(getNativeType(JSTypeNative.NULL_TYPE)); register(getNativeType(JSTypeNative.NULL_TYPE), "Null"); register(getNativeType(JSTypeNative.NUMBER_OBJECT_TYPE)); register(getNativeType(JSTypeNative.NUMBER_TYPE)); register(getNativeType(JSTypeNative.OBJECT_TYPE)); register(getNativeType(JSTypeNative.ERROR_TYPE)); register(getNativeType(JSTypeNative.URI_ERROR_TYPE)); register(getNativeType(JSTypeNative.EVAL_ERROR_TYPE)); register(getNativeType(JSTypeNative.TYPE_ERROR_TYPE)); register(getNativeType(JSTypeNative.RANGE_ERROR_TYPE)); register(getNativeType(JSTypeNative.REFERENCE_ERROR_TYPE)); register(getNativeType(JSTypeNative.SYNTAX_ERROR_TYPE)); register(getNativeType(JSTypeNative.REGEXP_TYPE)); register(getNativeType(JSTypeNative.STRING_OBJECT_TYPE)); register(getNativeType(JSTypeNative.STRING_TYPE)); register(getNativeType(JSTypeNative.VOID_TYPE)); register(getNativeType(JSTypeNative.VOID_TYPE), "Undefined"); register(getNativeType(JSTypeNative.VOID_TYPE), "void"); register(getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), "Function"); } private void register(JSType type) { register(type, type.toString()); } private void register(JSType type, String name) { Preconditions.checkArgument( !name.contains("<"), "Type names

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> example, traversing an IF node as root will visit the two subtrees * pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and * {@link ControlFlowGraph.Branch#ON_FALSE} edges. */ public abstract static class AbstractCfgNodeTraversalCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (parent == null) { return true; } return !isEnteringNewCfgNode(n); } } /** * @return True if n should be represented by a new CFG node in the control * flow graph. */ public static boolean isEnteringNewCfgNode(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.SCRIPT: case Token.TRY: return true; case Token.FUNCTION: // A function node represents the start of a function where the name // bleeds into the local scope and parameters are assigned // to the formal argument names. The node includes the name of the // function and the LP list since we assume the whole set up process // is atomic without change in control flow. The next change of // control is going into the function's body, represented by the second // child. return n != parent.getFirstChild().getNext(); case Token.WHILE: case Token.DO: case Token.IF: // These control structures are represented by a node that holds the // condition. Each of them is a branch node based on its condition. return NodeUtil.getConditionExpression(parent) != n; case Token.FOR: // The FOR(;;) node differs from other control structures in that // it has an initialization and an increment statement. Those // two statements have corresponding CFG nodes to represent them. // The FOR node only represents the condition check for each iteration. // That way the following: // for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to // var x = 0; while(x<10) { x++; } if (NodeUtil.isForIn(parent)) { // TODO(user): Investigate how we should handle the case where

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> // we have a very complex expression inside the FOR-IN header. return n != parent.getFirstChild(); } else { return NodeUtil.getConditionExpression(parent) != n; } case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.WITH: return n != parent.getFirstChild(); default: return false; } } @Override public String toString() { String s = "CFG:\n"; for (GraphvizEdge e : getGraphvizEdges()) { s += e.toString() + '\n'; } return s; } }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>, JSType type) { Preconditions.checkNotNull(node); Preconditions.checkNotNull(type); this.node = node; this.type = type; // Other parts of this pass may read off the node. // (like when we set the LHS of an assign with a typed RHS function.) node.setJSType(type); } void resolve(Scope scope) { node.setJSType(type.resolve(typeParsingErrorReporter, scope)); } } TypedScopeCreator(AbstractCompiler compiler) { this(compiler, compiler.getCodingConvention()); } TypedScopeCreator(AbstractCompiler compiler, CodingConvention codingConvention) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.codingConvention = codingConvention; this.typeRegistry = compiler.getTypeRegistry(); this.typeParsingErrorReporter = typeRegistry.getErrorReporter(); this.unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE); } /** * Creates a scope with all types declared. Declares newly discovered types * and type properties in the type registry. */ @Override public Scope createScope(Node root, Scope parent) { // Constructing the global scope is very different than constructing // inner scopes, because only global scopes can contain named classes that // show up in the type registry. Scope newScope = null; AbstractScopeBuilder scopeBuilder = null; if (parent == null) { JSType globalThis = typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS); // Mark the main root, the externs root, and the src root // with the global this type. root.setJSType(globalThis); root.getFirstChild().setJSType(globalThis); root.getLastChild().setJSType(globalThis); // Run a first-order analysis over the syntax tree. (new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults)) .process(root.getFirstChild(), root.getLastChild()); // Find all the classes in the global scope. newScope = createInitialScope(root); GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope); scopeBuilder = globalScopeBuilder; NodeTraversal.traverse(compiler, root, scopeBuilder); } else { new

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Scope = new Scope(parent, root); LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope); scopeBuilder = localScopeBuilder; localScopeBuilder.build(); } scopeBuilder.resolveStubDeclarations(); // Gather the properties in each function that we found in the // global scope, if that function has a @this type that we can // build properties on. for (Node functionNode : scopeBuilder.nonExternFunctions) { JSType type = functionNode.getJSType(); if (type != null && type.isFunctionType()) { FunctionType fnType = type.toMaybeFunctionType(); JSType fnThisType = fnType.getTypeOfThis(); if (!fnThisType.isUnknownType()) { NodeTraversal.traverse(compiler, functionNode.getLastChild(), scopeBuilder.new CollectProperties(fnThisType)); } } } if (parent == null) { codingConvention.defineDelegateProxyPrototypeProperties( typeRegistry, newScope, delegateProxyPrototypes, delegateCallingConventions); } newScope.setTypeResolver(scopeBuilder); return newScope; } /** * Patches a given global scope by removing variables previously declared in * a script and re-traversing a new version of that script. * * @param globalScope The global scope generated by {@code createScope}. * @param scriptRoot The script that is modified. */ void patchGlobalScope(Scope globalScope, Node scriptRoot) { // Preconditions: This is supposed to be called only on (named) SCRIPT nodes // and a global typed scope should have been generated already. Preconditions.checkState(scriptRoot.isScript()); Preconditions.checkNotNull(globalScope); Preconditions.checkState(globalScope.isGlobal()); String scriptName = NodeUtil.getSourceName(scriptRoot); Preconditions.checkNotNull(scriptName); for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) { if (scriptName.equals(NodeUtil.getSourceName(node))) { functionAnalysisResults.remove(node); } } (new FirstOrderFunctionAnalyzer( compiler, functionAnalysisResults)).process(null, scriptRoot); // TODO(bashir): Variable declaration is not the only side effect of last // global scope generation but

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> need to special case ActiveXObject // but this remains here until we can get the extern forks // cleaned up. declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE); return s; } private void declareNativeFunctionType(Scope scope, JSTypeNative tId) { FunctionType t = typeRegistry.getNativeFunctionType(tId); declareNativeType(scope, t.getInstanceType().getReferenceName(), t); declareNativeType( scope, t.getPrototype().getReferenceName(), t.getPrototype()); } private void declareNativeValueType(Scope scope, String name, JSTypeNative tId) { declareNativeType(scope, name, typeRegistry.getNativeType(tId)); } private static void declareNativeType(Scope scope, String name, JSType t) { scope.declare(name, null, t, null, false); } private static class DiscoverEnumsAndTypedefs extends AbstractShallowStatementCallback { private final JSTypeRegistry registry; DiscoverEnumsAndTypedefs(JSTypeRegistry registry) { this.registry = registry; } @Override public void visit(NodeTraversal t, Node node, Node parent) { switch (node.getType()) { case Token.VAR: for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { identifyNameNode( child, NodeUtil.getBestJSDocInfo(child)); } break; case Token.EXPR_RESULT: Node firstChild = node.getFirstChild(); if (firstChild.isAssign()) { identifyNameNode( firstChild.getFirstChild(), firstChild.getJSDocInfo()); } else { identifyNameNode( firstChild, firstChild.getJSDocInfo()); } break; } } private void identifyNameNode( Node nameNode, JSDocInfo info) { if (nameNode.isQualifiedName()) { if (info != null) { if (info.hasEnumParameterType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } else if (info.hasTypedefType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } } } } private

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Traverse(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); if (n.isFunction() || n.isScript()) { Preconditions.checkNotNull(inputId); sourceName = NodeUtil.getSourceName(n); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. boolean descend = parent == null || !parent.isFunction() || n == parent.getFirstChild() || parent == scope.getRootNode(); if (descend) { // Handle hoisted functions on pre-order traversal, so that they // get hit before other things in the scope. if (NodeUtil.isStatementParent(n)) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (NodeUtil.isHoistedFunctionDeclaration(child)) { defineFunctionLiteral(child); } } } } return descend; } @Override public void visit(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); attachLiteralTypes(n); switch (n.getType()) { case Token.CALL: checkForClassDefiningCalls(t, n); checkForCallingConventionDefiningCalls(n, delegateCallingConventions); break; case Token.FUNCTION: if (t.getInput() == null || !t.getInput().isExtern()) { nonExternFunctions.add(n); } // Hoisted functions are handled during pre-traversal. if (!NodeUtil.isHoistedFunctionDeclaration(n)) { defineFunctionLiteral(n); } break; case Token.ASSIGN: // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.isGetProp() && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: defineCatch(n); break; case Token.VAR: defineVar(n); break; case Token.GETPROP: // Handle stubbed properties. if (parent.isExprResult() &&

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> n.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null); } break; } // Analyze any @lends object literals in this statement. if (n.getParent() != null && NodeUtil.isStatement(n) && lentObjectLiterals != null) { for (Node objLit : lentObjectLiterals) { defineObjectLiteral(objLit); } lentObjectLiterals.clear(); } } private void attachLiteralTypes(Node n) { switch (n.getType()) { case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(VOID_TYPE)); break; case Token.STRING: n.setJSType(getNativeType(STRING_TYPE)); break; case Token.NUMBER: n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.TRUE: case Token.FALSE: n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE)); break; case Token.OBJECTLIT: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.getLendsName() != null) { if (lentObjectLiterals == null) { lentObjectLiterals = Lists.newArrayList(); } lentObjectLiterals.add(n); } else { defineObjectLiteral(n); } break; // NOTE(nicksantos): If we ever support Array tuples, // we will need to put ARRAYLIT here as well. } } private void defineObjectLiteral(Node objectLit) { // Handle the @lends annotation. JSType type = null; JSDocInfo info = objectLit.getJSDocInfo(); if (info != null && info.getLendsName() != null) { String lendsName = info.getLendsName(); Var lendsVar = scope.getVar(lendsName); if (lendsVar == null

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> be inferred. */ private void defineSlot(Node name, Node parent, JSType type) { defineSlot(name, parent, type, type == null); } /** * Defines a typed variable. The defining node will be annotated with the * variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is * inferred. * * Slots may be any variable or any qualified name in the global scope. * * @param n the defining NAME or GETPROP node. * @param parent the {@code n}'s parent. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. */ void defineSlot(Node n, Node parent, JSType type, boolean inferred) { Preconditions.checkArgument(inferred || type != null); // Only allow declarations of NAMEs and qualified names. // Object literal keys will have to compute their names themselves. if (n.isName()) { Preconditions.checkArgument( parent.isFunction() || parent.isVar() || parent.isParamList() || parent.isCatch()); } else { Preconditions.checkArgument( n.isGetProp() && (parent.isAssign() || parent.isExprResult())); } defineSlot(n, parent, n.getQualifiedName(), type, inferred); } /** * Defines a symbol in the current scope. * * @param n the defining NAME or GETPROP or object literal key node. * @param parent the {@code n}'s parent. * @param variableName The name that this should be known by. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. * @param inferred Whether the type is inferred or declared. */ void defineSlot(Node n, Node parent, String variableName, JSType type, boolean inferred) { Preconditions.checkArgument(!variableName.isEmpty()); boolean isGlobalVar = n.isName() && scope.isGlobal(); boolean shouldDeclareOnGlobalThis = isGlobalVar && (parent.isVar() || parent.isFunction()); // If n is a property, then we should really declare it in the

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> // scope where the root object appears. This helps out people // who declare "global" names in an anonymous namespace. Scope scopeToDeclareIn = scope; if (n.isGetProp() && !scope.isGlobal() && isQnameRootedInGlobalScope(n)) { Scope globalScope = scope.getGlobalScope(); // don't try to declare in the global scope if there's // already a symbol there with this name. if (!globalScope.isDeclared(variableName, false)) { scopeToDeclareIn = scope.getGlobalScope(); } } // The input may be null if we are working with a AST snippet. So read // the extern info from the node. Var newVar = null; // declared in closest scope? CompilerInput input = compiler.getInput(inputId); if (scopeToDeclareIn.isDeclared(variableName, false)) { Var oldVar = scopeToDeclareIn.getVar(variableName); newVar = validator.expectUndeclaredVariable( sourceName, input, n, parent, oldVar, variableName, type); } else { if (type != null) { setDeferredType(n, type); } newVar = scopeToDeclareIn.declare(variableName, n, type, input, inferred); if (type instanceof EnumType) { Node initialValue = newVar.getInitialValue(); boolean isValidValue = initialValue != null && (initialValue.isObjectLit() || initialValue.isQualifiedName()); if (!isValidValue) { compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER)); } } } // We need to do some additional work for constructors and interfaces. FunctionType fnType = JSType.toMaybeFunctionType(type); if (fnType != null && // We don't want to look at empty function types. !type.isEmptyType()) { // We want to make sure that when we declare a new instance type // (with @constructor) that there's actually a ctor for it. // This doesn't apply to structural constructors (like // function(new:Array). Checking the constructed type against // the variable name is a sufficient check for this. if ((fnType.isConstructor() || fnType.is

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> delegateSuperObject, delegateBaseObject, delegatorObject, delegateProxy, findDelegate); delegateProxyPrototypes.add(delegateProxy.getPrototype()); } } } /** * Declare the symbol for a qualified name in the global scope. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param parent The parent of {@code n}. * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { Node ownerNode = n.getFirstChild(); String ownerName = ownerNode.getQualifiedName(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); Preconditions.checkArgument(qName != null && ownerName != null); // Precedence of type information on GETPROPs: // 1) @type annotation / @enum annotation // 2) ASSIGN to FUNCTION literal // 3) @param/@return annotation (with no function literal) // 4) ASSIGN to something marked @const // 5) ASSIGN to anything else // // 1, 3, and 4 are declarations, 5 is inferred, and 2 is a declaration iff // the function has JsDoc or has not been declared before. // // FUNCTION literals are special because TypedScopeCreator is very smart // about getting as much type information as possible for them. // Determining type for #1 + #2 + #3 + #4 JSType valueType = getDeclaredType(info, n, rhsValue); if (valueType == null && rhsValue != null) { // Determining type for #5 valueType = rhsValue.getJSType(); } // Function prototypes are special. // It's a common JS idiom to do: // F.prototype = { ... }; // So if F does not have an explicitly declared super type, // allow F.prototype to be redefined arbitrarily. if ("prototype".equals(propName)) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Var qVar = scope.getVar(qName); if (qVar != null) { // If the programmer has declared that F inherits from Super, // and they assign F.prototype to an object literal, // then they are responsible for making sure that the object literal's // implicit prototype is set up appropriately. We just obey // the @extends tag. ObjectType qVarType = ObjectType.cast(qVar.getType()); if (qVarType != null && rhsValue != null && rhsValue.isObjectLit()) { typeRegistry.resetImplicitPrototype( rhsValue.getJSType(), qVarType.getImplicitPrototype()); } else if (!qVar.isTypeInferred()) { // If the programmer has declared that F inherits from Super, // and they assign F.prototype to some arbitrary expression, // there's not much we can do. We just ignore the expression, // and hope they've annotated their code in a way to tell us // what props are going to be on that prototype. return; } qVar.getScope().undeclare(qVar); } } if (valueType == null) { if (parent.isExprResult()) { stubDeclarations.add(new StubDeclaration( n, t.getInput() != null && t.getInput().isExtern(), ownerName)); } return; } boolean inferred = isQualifiedNameInferred( qName, n, info, rhsValue, valueType); if (!inferred) { ObjectType ownerType = getObjectSlot(ownerName); if (ownerType != null) { // Only declare this as an official property if it has not been // declared yet. boolean isExtern = t.getInput() != null && t.getInput().isExtern(); if ((!ownerType.hasOwnProperty(propName) || ownerType.isPropertyTypeInferred(propName)) && ((isExtern && !ownerType.isNativeObjectType()) || !ownerType.isInstanceType())) { // If the property is undeclared or inferred, declare it now. ownerType.defineDeclaredProperty(propName, valueType, n); } } // If the property is already declared, the error will be // caught when we try to declare it in the current scope. define

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Slot(n, parent, valueType, inferred); } else if (rhsValue != null && rhsValue.isTrue()) { // We declare these for delegate proxy method properties. ObjectType ownerType = getObjectSlot(ownerName); FunctionType ownerFnType = JSType.toMaybeFunctionType(ownerType); if (ownerFnType != null) { JSType ownerTypeOfThis = ownerFnType.getTypeOfThis(); String delegateName = codingConvention.getDelegateSuperclassName(); JSType delegateType = delegateName == null ? null : typeRegistry.getType(delegateName); if (delegateType != null && ownerTypeOfThis.isSubtype(delegateType)) { defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true); } } } } /** * Determines whether a qualified name is inferred. * NOTE(nicksantos): Determining whether a property is declared or not * is really really obnoxious. * * The problem is that there are two (equally valid) coding styles: * * (function() { * /* The authoritative definition of goog.bar. / * goog.bar = function() {}; * })(); * * function f() { * goog.bar(); * /* Reset goog.bar to a no-op. / * goog.bar = function() {}; * } * * In a dynamic language with first-class functions, it's very difficult * to know which one the user intended without looking at lots of * contextual information (the second example demonstrates a small case * of this, but there are some really pathological cases as well). * * The current algorithm checks if either the declaration has * JsDoc type information, or @const with a known type, * or a function literal with a name we haven't seen before. */ private boolean isQualifiedNameInferred( String qName, Node n, JSDocInfo info, Node rhsValue, JSType valueType) { if (valueType == null) { return true; } // Prototypes of constructors and interfaces are always declared. if (qName != null && qName.endsWith(".prototype")) { String className = qName.substring(0, qName.lastIndexOf(".prototype"));

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> Var slot = scope.getSlot(className); JSType classType = slot == null ? null : slot.getType(); if (classType != null && (classType.isConstructor() || classType.isInterface())) { return false; } } boolean inferred = true; if (info != null) { inferred = !(info.hasType() || info.hasEnumParameterType() || (isConstantSymbol(info, n) && valueType != null && !valueType.isUnknownType()) || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred && rhsValue != null && rhsValue.isFunction()) { if (info != null) { return false; } else if (!scope.isDeclared(qName, false) && n.isUnscopedQualifiedName()) { // Check if this is in a conditional block. // Functions assigned in conditional blocks are inferred. for (Node current = n.getParent(); !(current.isScript() || current.isFunction()); current = current.getParent()) { if (NodeUtil.isControlStructure(current)) { return true; } } // Check if this is assigned in an inner scope. // Functions assigned in inner scopes are inferred. AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents == null || !contents.getEscapedQualifiedNames().contains(qName)) { return false; } } } return inferred; } private boolean isConstantSymbol(JSDocInfo info, Node node) { if (info != null && info.isConstant()) { return true; } switch (node.getType()) { case Token.NAME: return NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node, node.getParent()); case Token.GETPROP: return node.isQualifiedName() && NodeUtil.isConstantByConvention( compiler.getCodingConvention(), node.getLastChild(), node); } return false; } /** * Find the ObjectType associated with the given slot. * @param slotName The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>String slotName) { Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Resolve any stub declarations to unknown types if we could not * find types for them during traversal. */ void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); String ownerName = stub.ownerName; boolean isExtern = stub.isExtern; if (scope.isDeclared(qName, false)) { continue; } // If we see a stub property, make sure to register this property // in the type registry. ObjectType ownerType = getObjectSlot(ownerName); defineSlot(n, parent, unknownType, true); if (ownerType != null && (isExtern || ownerType.isFunctionPrototypeType())) { // If this is a stub for a prototype, just declare it // as an unknown type. These are seen often in externs. ownerType.defineInferredProperty( propName, unknownType, n); } else { typeRegistry.registerPropertyOnType( propName, ownerType == null ? unknownType : ownerType); } } } /** * Collects all declared properties in a function, and * resolves them relative to the global scope. */ private final class CollectProperties extends AbstractShallowStatementCallback { private final JSType thisType; CollectProperties(JSType thisType) { this.thisType = thisType; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isExprResult()) { Node child = n.getFirstChild(); switch (child.getType()) { case Token.ASSIGN: maybeCollectMember(child.getFirstChild(), child, child.getLastChild()); break; case Token.GETPROP: maybeCollectMember(child, child, null); break; }

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> to the * global symbol table. * * @param t The current traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { super.visit(t, n, parent); switch (n.getType()) { case Token.VAR: // Handle typedefs. if (n.hasOneChild()) { checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo()); } break; } } @Override void maybeDeclareQualifiedName( NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { checkForTypedef(t, n, info); super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue); } /** * Handle typedefs. * @param t The current traversal. * @param candidate A qualified name node. * @param info JSDoc comments. */ private void checkForTypedef( NodeTraversal t, Node candidate, JSDocInfo info) { if (info == null || !info.hasTypedefType()) { return; } String typedef = candidate.getQualifiedName(); if (typedef == null) { return; } // TODO(nicksantos|user): This is a terrible, terrible hack // to bail out on recursive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, unknownType); JSType realType = info.getTypedefType().evaluate(scope, typeRegistry); if (realType == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); if (candidate.isGetProp()) { defineSlot(candidate, candidate.getParent(), getNativeType(NO_TYPE), false); } } } // end GlobalScopeBuilder /** * A shallow traversal of a local scope to find all arguments and * local variables. */ private final class LocalScopeBuilder extends AbstractScopeBuilder { /** * @param scope The scope that we're building.

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> */ private LocalScopeBuilder(Scope scope) { super(scope); } /** * Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, scope.getRootNode(), this); AstFunctionContents contents = getFunctionAnalysisResults(scope.getRootNode()); if (contents != null) { for (String varName : contents.getEscapedVarNames()) { Var v = scope.getVar(varName); Preconditions.checkState(v.getScope() == scope); v.markEscaped(); } for (Multiset.Entry<String> entry : contents.getAssignedNameCounts().entrySet()) { Var v = scope.getVar(entry.getElement()); Preconditions.checkState(v.getScope() == scope); if (entry.getCount() == 1) { v.markAssignedExactlyOnce(); } } } } /** * Visit a node in a local scope, and add any local variables or catch * parameters into the local symbol table. * * @param t The node traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n == scope.getRootNode()) { return; } if (n.isParamList() && parent == scope.getRootNode()) { handleFunctionInputs(parent); return; } super.visit(t, n, parent); } /** Handle bleeding functions and function parameters. */ private void handleFunctionInputs(Node fnNode) { // Handle bleeding functions. Node fnNameNode = fnNode.getFirstChild(); String fnName = fnNameNode.getString(); if (!fnName.isEmpty()) { Scope.Var fnVar = scope.getVar(fnName); if (fnVar == null || // Make sure we're not touching a native function. Native // functions aren't bleeding, but may not have a declaration // node. (fnVar.getNameNode() != null && // Make sure that the function is actually bleeding by checking // if has already been declared. fnVar.getInitialValue() != fnNode)) { define

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS>Slot(fnNameNode, fnNode, fnNode.getJSType(), false); } } declareArguments(fnNode); } /** * Declares all of a function's arguments. */ private void declareArguments(Node functionNode) { Node astParameters = functionNode.getFirstChild().getNext(); Node iifeArgumentNode = null; if (NodeUtil.isCallOrNewTarget(functionNode)) { iifeArgumentNode = functionNode.getNext(); } FunctionType functionType = JSType.toMaybeFunctionType(functionNode.getJSType()); if (functionType != null) { Node jsDocParameters = functionType.getParametersNode(); if (jsDocParameters != null) { Node jsDocParameter = jsDocParameters.getFirstChild(); for (Node astParameter : astParameters.children()) { JSType paramType = jsDocParameter == null ? unknownType : jsDocParameter.getJSType(); boolean inferred = paramType == null || paramType == unknownType; if (iifeArgumentNode != null && inferred) { String argumentName = iifeArgumentNode.getQualifiedName(); Var argumentVar = argumentName == null || scope.getParent() == null ? null : scope.getParent().getVar(argumentName); if (argumentVar != null && !argumentVar.isTypeInferred()) { paramType = argumentVar.getType(); } } if (paramType == null) { paramType = unknownType; } defineSlot(astParameter, functionNode, paramType, inferred); if (jsDocParameter != null) { jsDocParameter = jsDocParameter.getNext(); } if (iifeArgumentNode != null) { iifeArgumentNode = iifeArgumentNode.getNext(); } } } } } // end declareArguments } // end LocalScopeBuilder /** * Does a first-order function analysis that just looks at simple things * like what variables are escaped, and whether 'this' is used. */ private static class FirstOrderFunctionAnalyzer extends AbstractScopedCallback implements CompilerPass { private final AbstractCompiler compiler; private final Map<Node, AstFunctionContents> data; FirstOrderFunctionAnalyzer( AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) {

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> this.compiler = compiler; this.data = outParam; } @Override public void process(Node externs, Node root) { if (externs == null) { NodeTraversal.traverse(compiler, root, this); } else { NodeTraversal.traverseRoots( compiler, ImmutableList.of(externs, root), this); } } @Override public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); data.put(n, new AstFunctionContents(n)); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (t.inGlobalScope()) { return; } if (n.isReturn() && n.getFirstChild() != null) { data.get(t.getScopeRoot()).recordNonEmptyReturn(); } if (t.getScopeDepth() <= 1) { // The first-order function analyzer looks at two types of variables: // // 1) Local variables that are assigned in inner scopes ("escaped vars") // // 2) Local variables that are assigned more than once. // // We treat all global variables as escaped by default, so there's // no reason to do this extra computation for them. return; } if (n.isName() && NodeUtil.isLValue(n) && // Be careful of bleeding functions, which create variables // in the inner scope, not the scope where the name appears. !NodeUtil.isBleedingFunctionName(n)) { String name = n.getString(); Scope scope = t.getScope(); Var var = scope.getVar(name); if (var != null) { Scope ownerScope = var.getScope(); if (ownerScope.isLocal()) { data.get(ownerScope.getRootNode()).recordAssignedName(name); } if (scope != ownerScope && ownerScope.isLocal()) { data.get(ownerScope.getRootNode()).recordEscapedVarName(name); } } } else if (n.isGetProp() && n.isUnscopedQualifiedName() && NodeUtil.isLValue(n)) { String name =

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> function (for interfaces) * It is only relevant for constructors. May not be {@code null}. */ private List<ObjectType> extendedInterfaces = ImmutableList.of(); /** * The types which are subtypes of this function. It is only relevant for * constructors and may be {@code null}. */ private List<FunctionType> subTypes; /** Creates an instance for a function that might be a constructor. */ FunctionType(JSTypeRegistry registry, String name, Node source, ArrowType arrowType, JSType typeOfThis, TemplateTypeMap templateTypeMap, boolean isConstructor, boolean nativeType) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE), nativeType, templateTypeMap); setPrettyPrint(true); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkNotNull(arrowType); this.source = source; if (isConstructor) { this.kind = Kind.CONSTRUCTOR; this.propAccess = PropAccess.ANY; this.typeOfThis = typeOfThis != null ? typeOfThis : new InstanceObjectType(registry, this, nativeType); } else { this.kind = Kind.ORDINARY; this.typeOfThis = typeOfThis != null ? typeOfThis : registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE); } this.call = arrowType; } /** Creates an instance for a function that is an interface. */ private FunctionType(JSTypeRegistry registry, String name, Node source, TemplateTypeMap typeParameters) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE), false, typeParameters); setPrettyPrint(true); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkArgument(name != null); this.source = source; this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null); this.kind = Kind.INTERFACE; this.typeOfThis = new InstanceObjectType(registry, this); } /** Creates an instance for a function that is an interface. */ static FunctionType forInterface( JSTypeRegistry registry, String name, Node source,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> private FunctionType tryMergeFunctionPiecewise( FunctionType other, boolean leastSuper) { Node newParamsNode = null; if (call.hasEqualParameters(other.call, EquivalenceMethod.IDENTITY)) { newParamsNode = call.parameters; } else { // If the parameters are not equal, don't try to merge them. // Someday, we should try to merge the individual params. return null; } JSType newReturnType = leastSuper ? call.returnType.getLeastSupertype(other.call.returnType) : call.returnType.getGreatestSubtype(other.call.returnType); JSType newTypeOfThis = null; if (isEquivalent(typeOfThis, other.typeOfThis)) { newTypeOfThis = typeOfThis; } else { JSType maybeNewTypeOfThis = leastSuper ? typeOfThis.getLeastSupertype(other.typeOfThis) : typeOfThis.getGreatestSubtype(other.typeOfThis); newTypeOfThis = maybeNewTypeOfThis; } boolean newReturnTypeInferred = call.returnTypeInferred || other.call.returnTypeInferred; return new FunctionType( registry, null, null, new ArrowType( registry, newParamsNode, newReturnType, newReturnTypeInferred), newTypeOfThis, null, false, false); } /** * Given a constructor or an interface type, get its superclass constructor * or {@code null} if none exists. */ public FunctionType getSuperClassConstructor() { Preconditions.checkArgument(isConstructor() || isInterface()); ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype(); if (maybeSuperInstanceType == null) { return null; } return maybeSuperInstanceType.getConstructor(); } /** * Given an interface and a property, finds the top-most super interface * that has the property defined (including this interface). */ public static ObjectType getTopDefiningInterface(ObjectType type, String propertyName) { ObjectType foundType = null; if (type.hasProperty(propertyName)) { foundType = type; } for (ObjectType interfaceType : type.getCtorExtendedInterfaces()) { if (interfaceType.hasProperty(propertyName)) { foundType = getTopDefiningInterface(interfaceType,

Closure, 173

<FILEB>
<CHANGES>
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
<CHANGEE>
<FILEE>
<FILEB>
<CHANGES>
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return tryRotateAssociativeOperator(node);
<CHANGEE>
<CHANGES>
}
private Node tryRotateAssociativeOperator(Node n) {
if (!late) {
return n;
}
Preconditions.checkArgument(NodeUtil.isAssociative(n.getType()));
Node rhs = n.getLastChild();
if (n.getType() == rhs.getType()) {
Node parent = n.getParent();
Node first = n.getFirstChild().detachFromParent();
Node second = rhs.getFirstChild().detachFromParent();
Node third = rhs.getLastChild().detachFromParent();
Node newLhs = new Node(n.getType(), first, second)
.copyInformationFrom(n);
Node newRoot = new Node(rhs.getType(), newLhs, third)
.copyInformationFrom(rhs);
parent.replaceChild(n, newRoot);
reportCodeChange();
return newRoot;
}
return n;
<CHANGEE>
<FILEE>
<FILEB> if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); <CHANGES> if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { <CHANGEE> // Assignments are the only right-associative binary operators addExpr(first, p, context); cc.addOp(opstr, true); addExpr(last, p, rhsContext); } else { unrollBinaryOperator(n, type, opstr, context, rhsContext, p, p + 1); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { <FILEE> <FILEB> // convert a NEW node into a CALL node case Token.CALL: Node result = tryFoldLiteralConstructor(node); if (result == node) { result = tryFoldSimpleFunctionCall(node); if (result == node) { result = tryFoldImmediateCallToBoundFunction(node); } } return result; case Token.RETURN: return tryReduceReturn(node); case Token.COMMA: return trySplitComma(node); case Token.NAME: return tryReplaceUndefined(node); case Token.ARRAYLIT: return tryMinimizeArrayLiteral(node); <CHANGES> <CHANGEE> default: return node; //<SCANS> propertyName); } } return foundType; } /** * Given a constructor or an interface type and a property, finds the * top-most superclass that has the property defined (including this * constructor). */ public ObjectType getTopMostDefiningType(String propertyName) { Preconditions.checkState(isConstructor() || isInterface()); Preconditions.checkArgument(getInstanceType().hasProperty(propertyName)); FunctionType ctor = this; if (isInterface()) { return getTopDefiningInterface(getInstanceType(), propertyName); } ObjectType topInstanceType = null; do { topInstanceType = ctor.getInstanceType(); ctor = ctor.getSuperClassConstructor(); } while (ctor != null && ctor.getPrototype().hasProperty(propertyName)); return topInstanceType; } /** * Two function types are equal if their signatures match. Since they don't * have signatures, two interfaces are equal if their names match. */ boolean checkFunctionEquivalenceHelper( FunctionType that, EquivalenceMethod eqMethod) { if (isConstructor()) { if (that.isConstructor()) { return this == that; } return false; } if (isInterface()) { if (that.isInterface()) { return getReferenceName().equals(that.getReferenceName()); } return false; } if (that.isInterface()) { return false; } return typeOfThis.checkEquivalenceHelper(that.typeOfThis, eqMethod) && call.checkArrowEquivalenceHelper(that.call, eqMethod); } @Override public int hashCode() { return isInterface() ? getReferenceName().hashCode() : call.hashCode(); } public boolean hasEqualCallType(FunctionType otherType) { return this.call.checkArrowEquivalenceHelper( otherType.call, EquivalenceMethod.IDENTITY); } /** * Informally, a function is represented by * {@code function (params): returnType} where the {@code params} is a comma * separated list of types, the first one being a special * {@code this:T} if the function expects a known type for {@code this}. */ @Override String toStringHelper(boolean forAnnotations) { if (!isPrettyPrint() || this ==